| | import { |
| | workAsyncStorage, |
| | type WorkStore, |
| | } from '../app-render/work-async-storage.external' |
| | import type { OpaqueFallbackRouteParams } from './fallback-params' |
| |
|
| | import { ReflectAdapter } from '../web/spec-extension/adapters/reflect' |
| | import { |
| | throwToInterruptStaticGeneration, |
| | postponeWithTracking, |
| | delayUntilRuntimeStage, |
| | } from '../app-render/dynamic-rendering' |
| |
|
| | import { |
| | workUnitAsyncStorage, |
| | type PrerenderStorePPR, |
| | type PrerenderStoreLegacy, |
| | type StaticPrerenderStoreModern, |
| | type StaticPrerenderStore, |
| | throwInvariantForMissingStore, |
| | type PrerenderStoreModernRuntime, |
| | type RequestStore, |
| | } from '../app-render/work-unit-async-storage.external' |
| | import { InvariantError } from '../../shared/lib/invariant-error' |
| | import { |
| | describeStringPropertyAccess, |
| | wellKnownProperties, |
| | } from '../../shared/lib/utils/reflect-utils' |
| | import { |
| | makeDevtoolsIOAwarePromise, |
| | makeHangingPromise, |
| | } from '../dynamic-rendering-utils' |
| | import { createDedupedByCallsiteServerErrorLoggerDev } from '../create-deduped-by-callsite-server-error-logger' |
| | import { dynamicAccessAsyncStorage } from '../app-render/dynamic-access-async-storage.external' |
| | import { RenderStage } from '../app-render/staged-rendering' |
| |
|
| | export type ParamValue = string | Array<string> | undefined |
| | export type Params = Record<string, ParamValue> |
| |
|
| | export function createParamsFromClient( |
| | underlyingParams: Params, |
| | workStore: WorkStore |
| | ): Promise<Params> { |
| | const workUnitStore = workUnitAsyncStorage.getStore() |
| | if (workUnitStore) { |
| | switch (workUnitStore.type) { |
| | case 'prerender': |
| | case 'prerender-client': |
| | case 'prerender-ppr': |
| | case 'prerender-legacy': |
| | return createStaticPrerenderParams( |
| | underlyingParams, |
| | workStore, |
| | workUnitStore |
| | ) |
| | case 'cache': |
| | case 'private-cache': |
| | case 'unstable-cache': |
| | throw new InvariantError( |
| | 'createParamsFromClient should not be called in cache contexts.' |
| | ) |
| | case 'prerender-runtime': |
| | throw new InvariantError( |
| | 'createParamsFromClient should not be called in a runtime prerender.' |
| | ) |
| | case 'request': |
| | if (process.env.NODE_ENV === 'development') { |
| | |
| | |
| | |
| | const devFallbackParams = workUnitStore.devFallbackParams |
| | return createRenderParamsInDev( |
| | underlyingParams, |
| | devFallbackParams, |
| | workStore, |
| | workUnitStore |
| | ) |
| | } else { |
| | return createRenderParamsInProd(underlyingParams) |
| | } |
| | default: |
| | workUnitStore satisfies never |
| | } |
| | } |
| | throwInvariantForMissingStore() |
| | } |
| |
|
| | |
| | export type CreateServerParamsForMetadata = typeof createServerParamsForMetadata |
| | export const createServerParamsForMetadata = createServerParamsForServerSegment |
| |
|
| | |
| | export function createServerParamsForRoute( |
| | underlyingParams: Params, |
| | workStore: WorkStore |
| | ): Promise<Params> { |
| | const workUnitStore = workUnitAsyncStorage.getStore() |
| | if (workUnitStore) { |
| | switch (workUnitStore.type) { |
| | case 'prerender': |
| | case 'prerender-client': |
| | case 'prerender-ppr': |
| | case 'prerender-legacy': |
| | return createStaticPrerenderParams( |
| | underlyingParams, |
| | workStore, |
| | workUnitStore |
| | ) |
| | case 'cache': |
| | case 'private-cache': |
| | case 'unstable-cache': |
| | throw new InvariantError( |
| | 'createServerParamsForRoute should not be called in cache contexts.' |
| | ) |
| | case 'prerender-runtime': |
| | return createRuntimePrerenderParams(underlyingParams, workUnitStore) |
| | case 'request': |
| | if (process.env.NODE_ENV === 'development') { |
| | |
| | |
| | |
| | const devFallbackParams = workUnitStore.devFallbackParams |
| | return createRenderParamsInDev( |
| | underlyingParams, |
| | devFallbackParams, |
| | workStore, |
| | workUnitStore |
| | ) |
| | } else { |
| | return createRenderParamsInProd(underlyingParams) |
| | } |
| | default: |
| | workUnitStore satisfies never |
| | } |
| | } |
| | throwInvariantForMissingStore() |
| | } |
| |
|
| | export function createServerParamsForServerSegment( |
| | underlyingParams: Params, |
| | workStore: WorkStore |
| | ): Promise<Params> { |
| | const workUnitStore = workUnitAsyncStorage.getStore() |
| | if (workUnitStore) { |
| | switch (workUnitStore.type) { |
| | case 'prerender': |
| | case 'prerender-client': |
| | case 'prerender-ppr': |
| | case 'prerender-legacy': |
| | return createStaticPrerenderParams( |
| | underlyingParams, |
| | workStore, |
| | workUnitStore |
| | ) |
| | case 'cache': |
| | case 'private-cache': |
| | case 'unstable-cache': |
| | throw new InvariantError( |
| | 'createServerParamsForServerSegment should not be called in cache contexts.' |
| | ) |
| | case 'prerender-runtime': |
| | return createRuntimePrerenderParams(underlyingParams, workUnitStore) |
| | case 'request': |
| | if (process.env.NODE_ENV === 'development') { |
| | |
| | |
| | |
| | const devFallbackParams = workUnitStore.devFallbackParams |
| | return createRenderParamsInDev( |
| | underlyingParams, |
| | devFallbackParams, |
| | workStore, |
| | workUnitStore |
| | ) |
| | } else { |
| | return createRenderParamsInProd(underlyingParams) |
| | } |
| | default: |
| | workUnitStore satisfies never |
| | } |
| | } |
| | throwInvariantForMissingStore() |
| | } |
| |
|
| | export function createPrerenderParamsForClientSegment( |
| | underlyingParams: Params |
| | ): Promise<Params> { |
| | const workStore = workAsyncStorage.getStore() |
| | if (!workStore) { |
| | throw new InvariantError( |
| | 'Missing workStore in createPrerenderParamsForClientSegment' |
| | ) |
| | } |
| |
|
| | const workUnitStore = workUnitAsyncStorage.getStore() |
| | if (workUnitStore) { |
| | switch (workUnitStore.type) { |
| | case 'prerender': |
| | case 'prerender-client': |
| | const fallbackParams = workUnitStore.fallbackRouteParams |
| | if (fallbackParams) { |
| | for (let key in underlyingParams) { |
| | if (fallbackParams.has(key)) { |
| | |
| | |
| | |
| | |
| | return makeHangingPromise( |
| | workUnitStore.renderSignal, |
| | workStore.route, |
| | '`params`' |
| | ) |
| | } |
| | } |
| | } |
| | break |
| | case 'cache': |
| | case 'private-cache': |
| | case 'unstable-cache': |
| | throw new InvariantError( |
| | 'createPrerenderParamsForClientSegment should not be called in cache contexts.' |
| | ) |
| | case 'prerender-ppr': |
| | case 'prerender-legacy': |
| | case 'prerender-runtime': |
| | case 'request': |
| | break |
| | default: |
| | workUnitStore satisfies never |
| | } |
| | } |
| | |
| | |
| | |
| | return Promise.resolve(underlyingParams) |
| | } |
| |
|
| | function createStaticPrerenderParams( |
| | underlyingParams: Params, |
| | workStore: WorkStore, |
| | prerenderStore: StaticPrerenderStore |
| | ): Promise<Params> { |
| | switch (prerenderStore.type) { |
| | case 'prerender': |
| | case 'prerender-client': { |
| | const fallbackParams = prerenderStore.fallbackRouteParams |
| | if (fallbackParams) { |
| | for (const key in underlyingParams) { |
| | if (fallbackParams.has(key)) { |
| | |
| | |
| | |
| | |
| | return makeHangingParams( |
| | underlyingParams, |
| | workStore, |
| | prerenderStore |
| | ) |
| | } |
| | } |
| | } |
| | break |
| | } |
| | case 'prerender-ppr': { |
| | const fallbackParams = prerenderStore.fallbackRouteParams |
| | if (fallbackParams) { |
| | for (const key in underlyingParams) { |
| | if (fallbackParams.has(key)) { |
| | return makeErroringParams( |
| | underlyingParams, |
| | fallbackParams, |
| | workStore, |
| | prerenderStore |
| | ) |
| | } |
| | } |
| | } |
| | break |
| | } |
| | case 'prerender-legacy': |
| | break |
| | default: |
| | prerenderStore satisfies never |
| | } |
| |
|
| | return makeUntrackedParams(underlyingParams) |
| | } |
| |
|
| | function createRuntimePrerenderParams( |
| | underlyingParams: Params, |
| | workUnitStore: PrerenderStoreModernRuntime |
| | ): Promise<Params> { |
| | return delayUntilRuntimeStage( |
| | workUnitStore, |
| | makeUntrackedParams(underlyingParams) |
| | ) |
| | } |
| |
|
| | function createRenderParamsInProd(underlyingParams: Params): Promise<Params> { |
| | return makeUntrackedParams(underlyingParams) |
| | } |
| |
|
| | function createRenderParamsInDev( |
| | underlyingParams: Params, |
| | devFallbackParams: OpaqueFallbackRouteParams | null | undefined, |
| | workStore: WorkStore, |
| | requestStore: RequestStore |
| | ): Promise<Params> { |
| | let hasFallbackParams = false |
| | if (devFallbackParams) { |
| | for (let key in underlyingParams) { |
| | if (devFallbackParams.has(key)) { |
| | hasFallbackParams = true |
| | break |
| | } |
| | } |
| | } |
| |
|
| | return makeDynamicallyTrackedParamsWithDevWarnings( |
| | underlyingParams, |
| | hasFallbackParams, |
| | workStore, |
| | requestStore |
| | ) |
| | } |
| |
|
| | interface CacheLifetime {} |
| | const CachedParams = new WeakMap<CacheLifetime, Promise<Params>>() |
| |
|
| | const fallbackParamsProxyHandler: ProxyHandler<Promise<Params>> = { |
| | get: function get(target, prop, receiver) { |
| | if (prop === 'then' || prop === 'catch' || prop === 'finally') { |
| | const originalMethod = ReflectAdapter.get(target, prop, receiver) |
| |
|
| | return { |
| | [prop]: (...args: unknown[]) => { |
| | const store = dynamicAccessAsyncStorage.getStore() |
| |
|
| | if (store) { |
| | store.abortController.abort( |
| | new Error(`Accessed fallback \`params\` during prerendering.`) |
| | ) |
| | } |
| |
|
| | return new Proxy( |
| | originalMethod.apply(target, args), |
| | fallbackParamsProxyHandler |
| | ) |
| | }, |
| | }[prop] |
| | } |
| |
|
| | return ReflectAdapter.get(target, prop, receiver) |
| | }, |
| | } |
| |
|
| | function makeHangingParams( |
| | underlyingParams: Params, |
| | workStore: WorkStore, |
| | prerenderStore: StaticPrerenderStoreModern |
| | ): Promise<Params> { |
| | const cachedParams = CachedParams.get(underlyingParams) |
| | if (cachedParams) { |
| | return cachedParams |
| | } |
| |
|
| | const promise = new Proxy( |
| | makeHangingPromise<Params>( |
| | prerenderStore.renderSignal, |
| | workStore.route, |
| | '`params`' |
| | ), |
| | fallbackParamsProxyHandler |
| | ) |
| |
|
| | CachedParams.set(underlyingParams, promise) |
| |
|
| | return promise |
| | } |
| |
|
| | function makeErroringParams( |
| | underlyingParams: Params, |
| | fallbackParams: OpaqueFallbackRouteParams, |
| | workStore: WorkStore, |
| | prerenderStore: PrerenderStorePPR | PrerenderStoreLegacy |
| | ): Promise<Params> { |
| | const cachedParams = CachedParams.get(underlyingParams) |
| | if (cachedParams) { |
| | return cachedParams |
| | } |
| |
|
| | const augmentedUnderlying = { ...underlyingParams } |
| |
|
| | |
| | |
| | |
| | const promise = Promise.resolve(augmentedUnderlying) |
| | CachedParams.set(underlyingParams, promise) |
| |
|
| | Object.keys(underlyingParams).forEach((prop) => { |
| | if (wellKnownProperties.has(prop)) { |
| | |
| | |
| | } else { |
| | if (fallbackParams.has(prop)) { |
| | Object.defineProperty(augmentedUnderlying, prop, { |
| | get() { |
| | const expression = describeStringPropertyAccess('params', prop) |
| | |
| | |
| | |
| | |
| | |
| | |
| | if (prerenderStore.type === 'prerender-ppr') { |
| | |
| | postponeWithTracking( |
| | workStore.route, |
| | expression, |
| | prerenderStore.dynamicTracking |
| | ) |
| | } else { |
| | |
| | throwToInterruptStaticGeneration( |
| | expression, |
| | workStore, |
| | prerenderStore |
| | ) |
| | } |
| | }, |
| | enumerable: true, |
| | }) |
| | } |
| | } |
| | }) |
| |
|
| | return promise |
| | } |
| |
|
| | function makeUntrackedParams(underlyingParams: Params): Promise<Params> { |
| | const cachedParams = CachedParams.get(underlyingParams) |
| | if (cachedParams) { |
| | return cachedParams |
| | } |
| |
|
| | const promise = Promise.resolve(underlyingParams) |
| | CachedParams.set(underlyingParams, promise) |
| |
|
| | return promise |
| | } |
| |
|
| | function makeDynamicallyTrackedParamsWithDevWarnings( |
| | underlyingParams: Params, |
| | hasFallbackParams: boolean, |
| | workStore: WorkStore, |
| | requestStore: RequestStore |
| | ): Promise<Params> { |
| | if (requestStore.asyncApiPromises && hasFallbackParams) { |
| | |
| | |
| | |
| | |
| | |
| | const sharedParamsParent = requestStore.asyncApiPromises.sharedParamsParent |
| | const promise: Promise<Params> = new Promise((resolve, reject) => { |
| | sharedParamsParent.then(() => resolve(underlyingParams), reject) |
| | }) |
| | |
| | promise.displayName = 'params' |
| | return instrumentParamsPromiseWithDevWarnings( |
| | underlyingParams, |
| | promise, |
| | workStore |
| | ) |
| | } |
| |
|
| | const cachedParams = CachedParams.get(underlyingParams) |
| | if (cachedParams) { |
| | return cachedParams |
| | } |
| |
|
| | |
| | |
| | |
| | const promise = hasFallbackParams |
| | ? makeDevtoolsIOAwarePromise( |
| | underlyingParams, |
| | requestStore, |
| | RenderStage.Runtime |
| | ) |
| | : |
| | Promise.resolve(underlyingParams) |
| |
|
| | const proxiedPromise = instrumentParamsPromiseWithDevWarnings( |
| | underlyingParams, |
| | promise, |
| | workStore |
| | ) |
| | CachedParams.set(underlyingParams, proxiedPromise) |
| | return proxiedPromise |
| | } |
| |
|
| | function instrumentParamsPromiseWithDevWarnings( |
| | underlyingParams: Params, |
| | promise: Promise<Params>, |
| | workStore: WorkStore |
| | ): Promise<Params> { |
| | |
| | const proxiedProperties = new Set<string>() |
| |
|
| | Object.keys(underlyingParams).forEach((prop) => { |
| | if (wellKnownProperties.has(prop)) { |
| | |
| | |
| | } else { |
| | proxiedProperties.add(prop) |
| | } |
| | }) |
| |
|
| | return new Proxy(promise, { |
| | get(target, prop, receiver) { |
| | if (typeof prop === 'string') { |
| | if ( |
| | |
| | proxiedProperties.has(prop) |
| | ) { |
| | const expression = describeStringPropertyAccess('params', prop) |
| | warnForSyncAccess(workStore.route, expression) |
| | } |
| | } |
| | return ReflectAdapter.get(target, prop, receiver) |
| | }, |
| | set(target, prop, value, receiver) { |
| | if (typeof prop === 'string') { |
| | proxiedProperties.delete(prop) |
| | } |
| | return ReflectAdapter.set(target, prop, value, receiver) |
| | }, |
| | ownKeys(target) { |
| | const expression = '`...params` or similar expression' |
| | warnForSyncAccess(workStore.route, expression) |
| | return Reflect.ownKeys(target) |
| | }, |
| | }) |
| | } |
| |
|
| | const warnForSyncAccess = createDedupedByCallsiteServerErrorLoggerDev( |
| | createParamsAccessError |
| | ) |
| |
|
| | function createParamsAccessError( |
| | route: string | undefined, |
| | expression: string |
| | ) { |
| | const prefix = route ? `Route "${route}" ` : 'This route ' |
| | return new Error( |
| | `${prefix}used ${expression}. ` + |
| | `\`params\` is a Promise and must be unwrapped with \`await\` or \`React.use()\` before accessing its properties. ` + |
| | `Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis` |
| | ) |
| | } |
| |
|