import { workAsyncStorage } from '../app-render/work-async-storage.external'
import { workUnitAsyncStorage } from '../app-render/work-unit-async-storage.external'
import { abortOnSynchronousPlatformIOAccess } from '../app-render/dynamic-rendering'
import { InvariantError } from '../../shared/lib/invariant-error'
import { RenderStage } from '../app-render/staged-rendering'
import { applyOwnerStack } from '../dynamic-rendering-utils'
type ApiType = 'time' | 'random' | 'crypto'
export function io(expression: string, type: ApiType) {
const workUnitStore = workUnitAsyncStorage.getStore()
const workStore = workAsyncStorage.getStore()
if (!workUnitStore || !workStore) {
return
}
switch (workUnitStore.type) {
case 'prerender':
case 'prerender-runtime': {
const prerenderSignal = workUnitStore.controller.signal
if (prerenderSignal.aborted === false) {
// If the prerender signal is already aborted we don't need to construct
// any stacks because something else actually terminated the prerender.
let message: string
switch (type) {
case 'time':
message = `Route "${workStore.route}" used ${expression} before accessing either uncached data (e.g. \`fetch()\`) or Request data (e.g. \`cookies()\`, \`headers()\`, \`connection()\`, and \`searchParams\`). Accessing the current time in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-current-time`
break
case 'random':
message = `Route "${workStore.route}" used ${expression} before accessing either uncached data (e.g. \`fetch()\`) or Request data (e.g. \`cookies()\`, \`headers()\`, \`connection()\`, and \`searchParams\`). Accessing random values synchronously in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-random`
break
case 'crypto':
message = `Route "${workStore.route}" used ${expression} before accessing either uncached data (e.g. \`fetch()\`) or Request data (e.g. \`cookies()\`, \`headers()\`, \`connection()\`, and \`searchParams\`). Accessing random cryptographic values synchronously in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-crypto`
break
default:
throw new InvariantError(
'Unknown expression type in abortOnSynchronousPlatformIOAccess.'
)
}
abortOnSynchronousPlatformIOAccess(
workStore.route,
expression,
applyOwnerStack(new Error(message)),
workUnitStore
)
}
break
}
case 'prerender-client': {
const prerenderSignal = workUnitStore.controller.signal
if (prerenderSignal.aborted === false) {
// If the prerender signal is already aborted we don't need to construct
// any stacks because something else actually terminated the prerender.
let message: string
switch (type) {
case 'time':
message = `Route "${workStore.route}" used ${expression} inside a Client Component without a Suspense boundary above it. See more info here: https://nextjs.org/docs/messages/next-prerender-current-time-client`
break
case 'random':
message = `Route "${workStore.route}" used ${expression} inside a Client Component without a Suspense boundary above it. See more info here: https://nextjs.org/docs/messages/next-prerender-random-client`
break
case 'crypto':
message = `Route "${workStore.route}" used ${expression} inside a Client Component without a Suspense boundary above it. See more info here: https://nextjs.org/docs/messages/next-prerender-crypto-client`
break
default:
throw new InvariantError(
'Unknown expression type in abortOnSynchronousPlatformIOAccess.'
)
}
abortOnSynchronousPlatformIOAccess(
workStore.route,
expression,
applyOwnerStack(new Error(message)),
workUnitStore
)
}
break
}
case 'request': {
const stageController = workUnitStore.stagedRendering
if (stageController && stageController.shouldTrackSyncInterrupt()) {
let message: string
if (
stageController.currentStage === RenderStage.Static ||
stageController.currentStage === RenderStage.EarlyStatic
) {
switch (type) {
case 'time':
message = `Route "${workStore.route}" used ${expression} before accessing either uncached data (e.g. \`fetch()\`) or Request data (e.g. \`cookies()\`, \`headers()\`, \`connection()\`, and \`searchParams\`). Accessing the current time in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-current-time`
break
case 'random':
message = `Route "${workStore.route}" used ${expression} before accessing either uncached data (e.g. \`fetch()\`) or Request data (e.g. \`cookies()\`, \`headers()\`, \`connection()\`, and \`searchParams\`). Accessing random values synchronously in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-random`
break
case 'crypto':
message = `Route "${workStore.route}" used ${expression} before accessing either uncached data (e.g. \`fetch()\`) or Request data (e.g. \`cookies()\`, \`headers()\`, \`connection()\`, and \`searchParams\`). Accessing random cryptographic values synchronously in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-crypto`
break
default:
throw new InvariantError(
'Unknown expression type in abortOnSynchronousPlatformIOAccess.'
)
}
} else {
// We're in the Runtime stage.
// We only error for Sync IO in the Runtime stage if the route has a runtime prefetch config.
// This check is implemented in `stageController.canSyncInterrupt()` --
// if runtime prefetching isn't enabled, then we won't get here.
let accessStatement: string
let additionalInfoLink: string
switch (type) {
case 'time':
accessStatement = 'the current time'
additionalInfoLink =
'https://nextjs.org/docs/messages/next-prerender-runtime-current-time'
break
case 'random':
accessStatement = 'random values synchronously'
additionalInfoLink =
'https://nextjs.org/docs/messages/next-prerender-runtime-random'
break
case 'crypto':
accessStatement = 'random cryptographic values synchronously'
additionalInfoLink =
'https://nextjs.org/docs/messages/next-prerender-runtime-crypto'
break
default:
throw new InvariantError(
'Unknown expression type in abortOnSynchronousPlatformIOAccess.'
)
}
message = `Route "${workStore.route}" used ${expression} before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, accessing ${accessStatement} in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: ${additionalInfoLink}`
}
const syncIOError = applyOwnerStack(new Error(message))
stageController.syncInterruptCurrentStageWithReason(syncIOError)
// A build-time validation render uses a 'request' store type, but may be abortable.
// If we're in the second, restarted render of the restart-on-cache miss flow,
// Sync IO is an error, and unlike dev, there's no need to continue the render past the sync IO,
// so we can abort it.
if (
workUnitStore.controller &&
!workUnitStore.controller.signal.aborted
) {
workUnitStore.controller.abort(syncIOError)
}
}
break
}
case 'validation-client':
case 'prerender-ppr':
case 'prerender-legacy':
case 'cache':
case 'private-cache':
case 'unstable-cache':
case 'generate-static-params':
break
default:
workUnitStore satisfies never
}
}