{"version":3,"sources":["../../../../src/server/app-render/postponed-state.ts"],"sourcesContent":["import type {\n  OpaqueFallbackRouteParamEntries,\n  OpaqueFallbackRouteParams,\n} from '../../server/request/fallback-params'\nimport { getDynamicParam } from '../../shared/lib/router/utils/get-dynamic-param'\nimport type { Params } from '../request/params'\nimport {\n  createPrerenderResumeDataCache,\n  createRenderResumeDataCache,\n  type PrerenderResumeDataCache,\n  type RenderResumeDataCache,\n} from '../resume-data-cache/resume-data-cache'\nimport { stringifyResumeDataCache } from '../resume-data-cache/resume-data-cache'\n\nexport enum DynamicState {\n  /**\n   * The dynamic access occurred during the RSC render phase.\n   */\n  DATA = 1,\n\n  /**\n   * The dynamic access occurred during the HTML shell render phase.\n   */\n  HTML = 2,\n}\n\n/**\n * The postponed state for dynamic data.\n */\nexport type DynamicDataPostponedState = {\n  /**\n   * The type of dynamic state.\n   */\n  readonly type: DynamicState.DATA\n\n  /**\n   * The immutable resume data cache.\n   */\n  readonly renderResumeDataCache: RenderResumeDataCache\n}\n\n/**\n * The postponed state for dynamic HTML.\n */\nexport type DynamicHTMLPostponedState = {\n  /**\n   * The type of dynamic state.\n   */\n  readonly type: DynamicState.HTML\n\n  /**\n   * The postponed data used by React.\n   */\n  readonly data: [\n    preludeState: DynamicHTMLPreludeState,\n    postponed: ReactPostponed,\n  ]\n\n  /**\n   * The immutable resume data cache.\n   */\n  readonly renderResumeDataCache: RenderResumeDataCache\n}\n\nexport const enum DynamicHTMLPreludeState {\n  Empty = 0,\n  Full = 1,\n}\n\ntype ReactPostponed = NonNullable<\n  import('react-dom/static').PrerenderResult['postponed']\n>\n\nexport type PostponedState =\n  | DynamicDataPostponedState\n  | DynamicHTMLPostponedState\n\nexport async function getDynamicHTMLPostponedState(\n  postponed: ReactPostponed,\n  preludeState: DynamicHTMLPreludeState,\n  fallbackRouteParams: OpaqueFallbackRouteParams | null,\n  resumeDataCache: PrerenderResumeDataCache | RenderResumeDataCache,\n  isCacheComponentsEnabled: boolean\n): Promise<string> {\n  const data: DynamicHTMLPostponedState['data'] = [preludeState, postponed]\n  const dataString = JSON.stringify(data)\n\n  // If there are no fallback route params, we can just serialize the postponed\n  // state as is.\n  if (!fallbackRouteParams || fallbackRouteParams.size === 0) {\n    // Serialized as `<postponedString.length>:<postponedString><renderResumeDataCache>`\n    return `${dataString.length}:${dataString}${await stringifyResumeDataCache(\n      createRenderResumeDataCache(resumeDataCache),\n      isCacheComponentsEnabled\n    )}`\n  }\n\n  const replacements: OpaqueFallbackRouteParamEntries = Array.from(\n    fallbackRouteParams.entries()\n  )\n  const replacementsString = JSON.stringify(replacements)\n\n  // Serialized as `<replacements.length><replacements><data>`\n  const postponedString = `${replacementsString.length}${replacementsString}${dataString}`\n\n  // Serialized as `<postponedString.length>:<postponedString><renderResumeDataCache>`\n  return `${postponedString.length}:${postponedString}${await stringifyResumeDataCache(resumeDataCache, isCacheComponentsEnabled)}`\n}\n\nexport async function getDynamicDataPostponedState(\n  resumeDataCache: PrerenderResumeDataCache | RenderResumeDataCache,\n  isCacheComponentsEnabled: boolean\n): Promise<string> {\n  return `4:null${await stringifyResumeDataCache(createRenderResumeDataCache(resumeDataCache), isCacheComponentsEnabled)}`\n}\n\nexport function parsePostponedState(\n  state: string,\n  interpolatedParams: Params,\n  maxPostponedStateSizeBytes: number | undefined\n): PostponedState {\n  try {\n    const postponedStringLengthMatch = state.match(/^([0-9]*):/)?.[1]\n    if (!postponedStringLengthMatch) {\n      // Do not include the raw state in the message: it can be large and may\n      // contain sensitive serialized data.\n      throw new Error(\n        'Invariant: invalid postponed state: missing length prefix'\n      )\n    }\n\n    const postponedStringLength = parseInt(postponedStringLengthMatch)\n\n    // We add a `:` to the end of the length as the first character of the\n    // postponed string is the length of the replacement entries.\n    const postponedString = state.slice(\n      postponedStringLengthMatch.length + 1,\n      postponedStringLengthMatch.length + postponedStringLength + 1\n    )\n\n    const renderResumeDataCache = createRenderResumeDataCache(\n      state.slice(\n        postponedStringLengthMatch.length + postponedStringLength + 1\n      ),\n      maxPostponedStateSizeBytes\n    )\n\n    try {\n      if (postponedString === 'null') {\n        return { type: DynamicState.DATA, renderResumeDataCache }\n      }\n\n      if (/^[0-9]/.test(postponedString)) {\n        const match = postponedString.match(/^([0-9]*)/)?.[1]\n        if (!match) {\n          throw new Error(\n            `Invariant: invalid postponed state ${JSON.stringify(postponedString)}`\n          )\n        }\n\n        // This is the length of the replacements entries.\n        const length = parseInt(match)\n        const replacements = JSON.parse(\n          postponedString.slice(\n            match.length,\n            // We then go to the end of the string.\n            match.length + length\n          )\n        ) as OpaqueFallbackRouteParamEntries\n\n        let postponed = postponedString.slice(match.length + length)\n        for (const [\n          segmentKey,\n          [searchValue, dynamicParamType],\n        ] of replacements) {\n          const {\n            treeSegment: [\n              ,\n              // This is the same value that'll be used in the postponed state\n              // as it's part of the tree data. That's why we use it as the\n              // replacement value.\n              value,\n            ],\n          } = getDynamicParam(\n            interpolatedParams,\n            segmentKey,\n            dynamicParamType,\n            null,\n            null // staticSiblings not needed for postponed state\n          )\n\n          postponed = postponed.replaceAll(searchValue, value)\n        }\n\n        return {\n          type: DynamicState.HTML,\n          data: JSON.parse(postponed),\n          renderResumeDataCache,\n        }\n      }\n\n      return {\n        type: DynamicState.HTML,\n        data: JSON.parse(postponedString),\n        renderResumeDataCache,\n      }\n    } catch (err) {\n      console.error(\n        'Failed to parse postponed state',\n        describePostponedStateParseFailure(state, err)\n      )\n      return { type: DynamicState.DATA, renderResumeDataCache }\n    }\n  } catch (err) {\n    console.error(\n      'Failed to parse postponed state',\n      describePostponedStateParseFailure(state, err)\n    )\n    return {\n      type: DynamicState.DATA,\n      renderResumeDataCache: createRenderResumeDataCache(\n        createPrerenderResumeDataCache()\n      ),\n    }\n  }\n}\n\n/**\n * Derives content-free diagnostics about a postponed state that failed to\n * parse, so the error log is actionable without exposing the (potentially\n * sensitive) serialized contents. Every field is a size, a structural flag, or\n * an error code, never the state bytes themselves.\n *\n * The serialized layout is `<N>:<postponedString><base64-deflate cache>`, so\n * these fields distinguish the failure shapes:\n * - `postponedStringComplete: false`: the declared length `N` exceeds what\n * actually arrived, i.e. the postponed string itself was truncated.\n * - `errorCode: 'Z_BUF_ERROR'` with an empty or short tail: the\n * resume-data-cache tail was truncated (ran out of input while inflating).\n * - `errorCode: 'Z_DATA_ERROR'`: the tail bytes are corrupt, not merely short.\n * - `hasLengthPrefix: false`: the body had no `<N>:` prefix at all (e.g. empty\n * or otherwise malformed input).\n */\nfunction describePostponedStateParseFailure(\n  state: string,\n  error: unknown\n): Record<string, unknown> {\n  const errnoError = error as NodeJS.ErrnoException | undefined\n  const diagnostics: Record<string, unknown> = {\n    stateLength: state.length,\n    errorName: error instanceof Error ? error.name : typeof error,\n    errorCode: errnoError?.code,\n  }\n\n  const prefixMatch = state.match(/^([0-9]+):/)\n  if (!prefixMatch) {\n    diagnostics.hasLengthPrefix = false\n    return diagnostics\n  }\n\n  const declaredPostponedLength = parseInt(prefixMatch[1], 10)\n  const tailStart = prefixMatch[0].length + declaredPostponedLength\n  const tail = state.slice(tailStart)\n\n  diagnostics.hasLengthPrefix = true\n  diagnostics.declaredPostponedLength = declaredPostponedLength\n  diagnostics.postponedStringComplete = state.length >= tailStart\n  diagnostics.resumeDataCacheTailLength = tail.length\n  diagnostics.resumeDataCacheTailIsNull = tail === 'null'\n\n  return diagnostics\n}\n\nexport function getPostponedFromState(state: DynamicHTMLPostponedState) {\n  const [preludeState, postponed] = state.data\n  return { preludeState, postponed }\n}\n\n/**\n * Cheaply determines whether a serialized postponed state represents an empty\n * HTML prelude — i.e. the static shell rendered no bytes before the first\n * dynamic hole (a blocking dynamic API at the root with no Suspense boundary\n * above it). Returns false for dynamic-data states or unparseable input.\n *\n * Unlike `parsePostponedState`, this does not interpolate fallback route params\n * or build a resume data cache: it only reads the prelude marker, which is\n * independent of param values. The Instant Navigation Testing API uses this to\n * detect the blank-document case in both dev (fresh render) and production\n * (prebuilt shell), where the marker is persisted in the postponed state.\n */\nexport function isEmptyHTMLPrelude(state: string): boolean {\n  try {\n    const lengthMatch = state.match(/^([0-9]*):/)?.[1]\n    if (!lengthMatch) {\n      return false\n    }\n\n    const length = parseInt(lengthMatch)\n    let postponedString = state.slice(\n      lengthMatch.length + 1,\n      lengthMatch.length + 1 + length\n    )\n\n    // `null` is the dynamic-data case (a full shell was produced).\n    if (postponedString === 'null') {\n      return false\n    }\n\n    // An optional `<n><replacements>` prefix carries fallback route param\n    // replacements; skip it to reach the `[preludeState, postponed]` data.\n    if (/^[0-9]/.test(postponedString)) {\n      const replacementsLengthMatch = postponedString.match(/^([0-9]*)/)?.[1]\n      if (!replacementsLengthMatch) {\n        return false\n      }\n      const replacementsLength = parseInt(replacementsLengthMatch)\n      postponedString = postponedString.slice(\n        replacementsLengthMatch.length + replacementsLength\n      )\n    }\n\n    const data = JSON.parse(postponedString)\n    return Array.isArray(data) && data[0] === DynamicHTMLPreludeState.Empty\n  } catch {\n    return false\n  }\n}\n"],"names":["getDynamicParam","createPrerenderResumeDataCache","createRenderResumeDataCache","stringifyResumeDataCache","DynamicState","DynamicHTMLPreludeState","getDynamicHTMLPostponedState","postponed","preludeState","fallbackRouteParams","resumeDataCache","isCacheComponentsEnabled","data","dataString","JSON","stringify","size","length","replacements","Array","from","entries","replacementsString","postponedString","getDynamicDataPostponedState","parsePostponedState","state","interpolatedParams","maxPostponedStateSizeBytes","postponedStringLengthMatch","match","Error","postponedStringLength","parseInt","slice","renderResumeDataCache","type","test","parse","segmentKey","searchValue","dynamicParamType","treeSegment","value","replaceAll","err","console","error","describePostponedStateParseFailure","errnoError","diagnostics","stateLength","errorName","name","errorCode","code","prefixMatch","hasLengthPrefix","declaredPostponedLength","tailStart","tail","postponedStringComplete","resumeDataCacheTailLength","resumeDataCacheTailIsNull","getPostponedFromState","isEmptyHTMLPrelude","lengthMatch","replacementsLengthMatch","replacementsLength","isArray"],"mappings":"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","ignoreList":[0]}