Skip to content

Commit

Permalink
Merge branch 'canary' into elef/introduce-error-builder
Browse files Browse the repository at this point in the history
  • Loading branch information
ijjk authored Jun 10, 2024
2 parents b7854dd + 755c9e4 commit af0155f
Show file tree
Hide file tree
Showing 9 changed files with 371 additions and 24 deletions.
2 changes: 0 additions & 2 deletions packages/next/src/server/base-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2795,8 +2795,6 @@ export default abstract class Server<
kind: 'PAGE',
html: RenderResult.fromStatic(''),
pageData: {},
// @ts-ignore To be removed
postponed: undefined,
headers: undefined,
status: undefined,
},
Expand Down
72 changes: 51 additions & 21 deletions packages/next/src/server/lib/incremental-cache/fetch-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,40 @@ const CACHE_REVALIDATE_HEADER = 'x-vercel-revalidate' as const
const CACHE_FETCH_URL_HEADER = 'x-vercel-cache-item-name' as const
const CACHE_CONTROL_VALUE_HEADER = 'x-vercel-cache-control' as const

const DEBUG = Boolean(process.env.NEXT_PRIVATE_DEBUG_CACHE)

async function fetchRetryWithTimeout(
url: Parameters<typeof fetch>[0],
init: Parameters<typeof fetch>[1],
retryIndex = 0
): Promise<Response> {
const controller = new AbortController()
const timeout = setTimeout(() => {
controller.abort()
}, 500)

return fetch(url, {
...(init || {}),
signal: controller.signal,
})
.catch((err) => {
if (retryIndex === 3) {
throw err
} else {
if (DEBUG) {
console.log(`Fetch failed for ${url} retry ${retryIndex}`)
}
return fetchRetryWithTimeout(url, init, retryIndex + 1)
}
})
.finally(() => {
clearTimeout(timeout)
})
}

export default class FetchCache implements CacheHandler {
private headers: Record<string, string>
private cacheEndpoint?: string
private debug: boolean

private hasMatchingTags(arr1: string[], arr2: string[]) {
if (arr1.length !== arr2.length) return false
Expand All @@ -53,7 +83,6 @@ export default class FetchCache implements CacheHandler {
}

constructor(ctx: CacheHandlerContext) {
this.debug = !!process.env.NEXT_PRIVATE_DEBUG_CACHE
this.headers = {}
this.headers['Content-Type'] = 'application/json'

Expand All @@ -79,17 +108,18 @@ export default class FetchCache implements CacheHandler {
}

if (scHost) {
this.cacheEndpoint = `https://${scHost}${scBasePath || ''}`
if (this.debug) {
const scProto = process.env.SUSPENSE_CACHE_PROTO || 'https'
this.cacheEndpoint = `${scProto}://${scHost}${scBasePath || ''}`
if (DEBUG) {
console.log('using cache endpoint', this.cacheEndpoint)
}
} else if (this.debug) {
} else if (DEBUG) {
console.log('no cache endpoint available')
}

if (ctx.maxMemoryCacheSize) {
if (!memoryCache) {
if (this.debug) {
if (DEBUG) {
console.log('using memory store for fetch cache')
}

Expand Down Expand Up @@ -118,7 +148,7 @@ export default class FetchCache implements CacheHandler {
})
}
} else {
if (this.debug) {
if (DEBUG) {
console.log('not using memory store for fetch cache')
}
}
Expand All @@ -133,21 +163,21 @@ export default class FetchCache implements CacheHandler {
) {
let [tags] = args
tags = typeof tags === 'string' ? [tags] : tags
if (this.debug) {
if (DEBUG) {
console.log('revalidateTag', tags)
}

if (!tags.length) return

if (Date.now() < rateLimitedUntil) {
if (this.debug) {
if (DEBUG) {
console.log('rate limited ', rateLimitedUntil)
}
return
}

try {
const res = await fetch(
const res = await fetchRetryWithTimeout(
`${this.cacheEndpoint}/v1/suspense-cache/revalidate?tags=${tags
.map((tag) => encodeURIComponent(tag))
.join(',')}`,
Expand Down Expand Up @@ -181,7 +211,7 @@ export default class FetchCache implements CacheHandler {
}

if (Date.now() < rateLimitedUntil) {
if (this.debug) {
if (DEBUG) {
console.log('rate limited')
}
return null
Expand Down Expand Up @@ -227,7 +257,7 @@ export default class FetchCache implements CacheHandler {
}

if (res.status === 404) {
if (this.debug) {
if (DEBUG) {
console.log(
`no fetch cache entry for ${key}, duration: ${
Date.now() - start
Expand All @@ -245,7 +275,7 @@ export default class FetchCache implements CacheHandler {
const cached: IncrementalCacheValue = await res.json()

if (!cached || cached.kind !== 'FETCH') {
this.debug && console.log({ cached })
DEBUG && console.log({ cached })
throw new Error('invalid cache value')
}

Expand All @@ -272,7 +302,7 @@ export default class FetchCache implements CacheHandler {
: Date.now() - parseInt(age || '0', 10) * 1000,
}

if (this.debug) {
if (DEBUG) {
console.log(
`got fetch cache entry for ${key}, duration: ${
Date.now() - start
Expand All @@ -289,7 +319,7 @@ export default class FetchCache implements CacheHandler {
}
} catch (err) {
// unable to get data from fetch-cache
if (this.debug) {
if (DEBUG) {
console.error(`Failed to get from fetch-cache`, err)
}
}
Expand All @@ -314,7 +344,7 @@ export default class FetchCache implements CacheHandler {
JSON.stringify((newValue as Record<string, string | Object>)[field])
)
) {
if (this.debug) {
if (DEBUG) {
console.log(`skipping cache set for ${key} as not modified`)
}
return
Expand All @@ -324,7 +354,7 @@ export default class FetchCache implements CacheHandler {
if (!fetchCache) return

if (Date.now() < rateLimitedUntil) {
if (this.debug) {
if (DEBUG) {
console.log('rate limited')
}
return
Expand Down Expand Up @@ -356,7 +386,7 @@ export default class FetchCache implements CacheHandler {
tags: undefined,
})

if (this.debug) {
if (DEBUG) {
console.log('set cache', key)
}
const fetchParams: NextFetchCacheParams = {
Expand Down Expand Up @@ -385,11 +415,11 @@ export default class FetchCache implements CacheHandler {
}

if (!res.ok) {
this.debug && console.log(await res.text())
DEBUG && console.log(await res.text())
throw new Error(`invalid response ${res.status}`)
}

if (this.debug) {
if (DEBUG) {
console.log(
`successfully set to fetch-cache for ${key}, duration: ${
Date.now() - start
Expand All @@ -398,7 +428,7 @@ export default class FetchCache implements CacheHandler {
}
} catch (err) {
// unable to set to fetch-cache
if (this.debug) {
if (DEBUG) {
console.error(`Failed to update fetch cache`, err)
}
}
Expand Down
10 changes: 9 additions & 1 deletion packages/next/src/server/web/spec-extension/unstable-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export function unstable_cache<T extends Callback>(
return a.localeCompare(b)
})
const sortedSearch = sortedSearchKeys
.map((key) => searchParams.get(key))
.map((key) => `${key}=${searchParams.get(key)}`)
.join('&')

// Construct the complete cache key for this function invocation
Expand Down Expand Up @@ -180,6 +180,7 @@ export function unstable_cache<T extends Callback>(
tags,
softTags: implicitTags,
fetchIdx,
fetchUrl,
})

if (cacheEntry && cacheEntry.value) {
Expand Down Expand Up @@ -276,10 +277,17 @@ export function unstable_cache<T extends Callback>(
if (!incrementalCache.isOnDemandRevalidate) {
// We aren't doing an on demand revalidation so we check use the cache if valid

// @TODO check on this API. addImplicitTags mutates the store and returns the implicit tags. The naming
// of this function is potentially a little confusing
const implicitTags = store && addImplicitTags(store)

const cacheEntry = await incrementalCache.get(cacheKey, {
kindHint: 'fetch',
revalidate: options.revalidate,
tags,
fetchIdx,
fetchUrl,
softTags: implicitTags,
})

if (cacheEntry && cacheEntry.value) {
Expand Down
11 changes: 11 additions & 0 deletions test/ppr-tests-manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
{
"version": 2,
"suites": {
"test/production/app-dir/fetch-cache/fetch-cache.test.ts": {
"passed": [],
"failed": [
"fetch-cache should have correct fetchUrl field for fetches and unstable_cache",
"fetch-cache should retry 3 times when revalidate times out",
"fetch-cache should not retry for failed fetch-cache GET"
],
"pending": [],
"flakey": [],
"runtimeError": false
},
"test/e2e/app-dir/app-static/app-static.test.ts": {
"failed": [
"app-dir static/dynamic handling usePathname should have values from canonical url on rewrite",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { revalidateTag } from 'next/cache'
import { NextRequest, NextResponse } from 'next/server'

export const dynamic = 'force-dynamic'

export function GET(req: NextRequest) {
revalidateTag('thankyounext')
return NextResponse.json({ done: true })
}
8 changes: 8 additions & 0 deletions test/production/app-dir/fetch-cache/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ReactNode } from 'react'
export default function Root({ children }: { children: ReactNode }) {
return (
<html>
<body>{children}</body>
</html>
)
}
32 changes: 32 additions & 0 deletions test/production/app-dir/fetch-cache/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { unstable_cache } from 'next/cache'

export const dynamic = 'force-dynamic'
export const fetchCache = 'default-cache'

const getCachedRandom = unstable_cache(
async () => {
return Math.random()
},
[],
{
revalidate: 3,
tags: ['thankyounext'],
}
)

export default async function Page() {
const data = await fetch(
'https://next-data-api-endpoint.vercel.app/api/random?a=b',
{ next: { tags: ['thankyounext'], revalidate: 3 } }
).then((res) => res.text())

const cachedRandom = getCachedRandom()

return (
<>
<p>hello world</p>
<p id="data">{data}</p>
<p id="random">{cachedRandom}</p>
</>
)
}
Loading

0 comments on commit af0155f

Please sign in to comment.