File size: 6,090 Bytes
b91e262
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
import { InvariantError } from '../../shared/lib/invariant-error'
import {
  postponeWithTracking,
  throwToInterruptStaticGeneration,
} from '../app-render/dynamic-rendering'
import {
  workAsyncStorage,
  type WorkStore,
} from '../app-render/work-async-storage.external'
import {
  workUnitAsyncStorage,
  type PrerenderStoreLegacy,
  type PrerenderStorePPR,
  type StaticPrerenderStore,
} from '../app-render/work-unit-async-storage.external'
import { makeHangingPromise } from '../dynamic-rendering-utils'
import type { ParamValue } from './params'
import { describeStringPropertyAccess } from '../../shared/lib/utils/reflect-utils'
import { actionAsyncStorage } from '../app-render/action-async-storage.external'

/**
 * Used for the compiler-generated `next/root-params` module.
 * @internal
 */
export function getRootParam(paramName: string): Promise<ParamValue> {
  const apiName = `\`import('next/root-params').${paramName}()\``

  const workStore = workAsyncStorage.getStore()
  if (!workStore) {
    throw new InvariantError(`Missing workStore in ${apiName}`)
  }

  const workUnitStore = workUnitAsyncStorage.getStore()
  if (!workUnitStore) {
    throw new Error(
      `Route ${workStore.route} used ${apiName} outside of a Server Component. This is not allowed.`
    )
  }

  const actionStore = actionAsyncStorage.getStore()
  if (actionStore) {
    if (actionStore.isAppRoute) {
      // TODO(root-params): add support for route handlers
      throw new Error(
        `Route ${workStore.route} used ${apiName} inside a Route Handler. Support for this API in Route Handlers is planned for a future version of Next.js.`
      )
    }
    if (actionStore.isAction && workUnitStore.phase === 'action') {
      // Actions are not fundamentally tied to a route (even if they're always submitted from some page),
      // so root params would be inconsistent if an action is called from multiple roots.
      // Make sure we check if the phase is "action" - we should not error in the rerender
      // after an action revalidates or updates cookies (which will still have `actionStore.isAction === true`)
      throw new Error(
        `${apiName} was used inside a Server Action. This is not supported. Functions from 'next/root-params' can only be called in the context of a route.`
      )
    }
  }

  switch (workUnitStore.type) {
    case 'unstable-cache':
    case 'cache': {
      throw new Error(
        `Route ${workStore.route} used ${apiName} inside \`"use cache"\` or \`unstable_cache\`. Support for this API inside cache scopes is planned for a future version of Next.js.`
      )
    }
    case 'prerender':
    case 'prerender-client':
    case 'prerender-ppr':
    case 'prerender-legacy': {
      return createPrerenderRootParamPromise(
        paramName,
        workStore,
        workUnitStore,
        apiName
      )
    }
    case 'private-cache':
    case 'prerender-runtime':
    case 'request': {
      break
    }
    default: {
      workUnitStore satisfies never
    }
  }
  return Promise.resolve(workUnitStore.rootParams[paramName])
}

function createPrerenderRootParamPromise(
  paramName: string,
  workStore: WorkStore,
  prerenderStore: StaticPrerenderStore,
  apiName: string
): Promise<ParamValue> {
  switch (prerenderStore.type) {
    case 'prerender-client': {
      throw new InvariantError(
        `${apiName} must not be used within a client component. Next.js should be preventing ${apiName} from being included in client components statically, but did not in this case.`
      )
    }
    case 'prerender':
    case 'prerender-legacy':
    case 'prerender-ppr':
    default:
  }

  const underlyingParams = prerenderStore.rootParams

  switch (prerenderStore.type) {
    case 'prerender': {
      // We are in a cacheComponents prerender.
      // The param is a fallback, so it should be treated as dynamic.
      if (
        prerenderStore.fallbackRouteParams &&
        prerenderStore.fallbackRouteParams.has(paramName)
      ) {
        return makeHangingPromise<ParamValue>(
          prerenderStore.renderSignal,
          workStore.route,
          apiName
        )
      }
      break
    }
    case 'prerender-ppr': {
      // We aren't in a cacheComponents prerender, but the param is a fallback,
      // so we need to make an erroring params object which will postpone/error if you access it
      if (
        prerenderStore.fallbackRouteParams &&
        prerenderStore.fallbackRouteParams.has(paramName)
      ) {
        return makeErroringRootParamPromise(
          paramName,
          workStore,
          prerenderStore,
          apiName
        )
      }
      break
    }
    case 'prerender-legacy': {
      // legacy prerenders can't have fallback params
      break
    }
    default: {
      prerenderStore satisfies never
    }
  }

  // If the param is not a fallback param, we just return the statically available value.
  return Promise.resolve(underlyingParams[paramName])
}

/** Deliberately async -- we want to create a rejected promise, not error synchronously. */
async function makeErroringRootParamPromise(
  paramName: string,
  workStore: WorkStore,
  prerenderStore: PrerenderStorePPR | PrerenderStoreLegacy,
  apiName: string
): Promise<ParamValue> {
  const expression = describeStringPropertyAccess(apiName, paramName)
  // In most dynamic APIs, we also throw if `dynamic = "error"`.
  // However, root params are only dynamic when we're generating a fallback shell,
  // and even with `dynamic = "error"` we still support generating dynamic fallback shells.
  // TODO: remove this comment when cacheComponents is the default since there will be no `dynamic = "error"`
  switch (prerenderStore.type) {
    case 'prerender-ppr': {
      return postponeWithTracking(
        workStore.route,
        expression,
        prerenderStore.dynamicTracking
      )
    }
    case 'prerender-legacy': {
      return throwToInterruptStaticGeneration(
        expression,
        workStore,
        prerenderStore
      )
    }
    default: {
      prerenderStore satisfies never
    }
  }
}