packages/react-server/src/ReactFlightReplyServer.js JAVASCRIPT 2,007 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 2,007.
1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @flow8 */910import type {Thenable} from 'shared/ReactTypes';1112// The server acts as a Client of itself when resolving Server References.13// That's why we import the Client configuration from the Server.14// Everything is aliased as their Server equivalence for clarity.15import type {16  ServerReferenceId,17  ServerManifest,18  ClientReference as ServerReference,19} from 'react-client/src/ReactFlightClientConfig';2021import type {BackingFormData} from './ReactFlightReplyBackingFormData';22import type {TemporaryReferenceSet} from './ReactFlightServerTemporaryReferences';2324import {25  resolveServerReference,26  preloadModule,27  requireModule,28} from 'react-client/src/ReactFlightClientConfig';2930import {31  createBackingFormData,32  advanceBackingEntryIterator,33  appendBackingEntry,34  appendBackingFile,35  consumeBackingEntry,36  getBackingEntry,37  getAllBackingEntries,38  peekBackingEntry,39} from './ReactFlightReplyBackingFormData';40import {41  createTemporaryReference,42  registerTemporaryReference,43} from './ReactFlightServerTemporaryReferences';44import {ASYNC_ITERATOR} from 'shared/ReactSymbols';4546import hasOwnProperty from 'shared/hasOwnProperty';47import getPrototypeOf from 'shared/getPrototypeOf';48import isArray from 'shared/isArray';4950interface FlightStreamController {51  enqueueModel(json: string): void;52  close(json: string): void;53  error(error: Error): void;54}5556export type JSONValue =57  | number58  | null59  | boolean60  | string61  | {+[key: string]: JSONValue}62  | $ReadOnlyArray<JSONValue>;6364const PENDING = 'pending';65const BLOCKED = 'blocked';66const RESOLVED_MODEL = 'resolved_model';67const INITIALIZED = 'fulfilled';68const ERRORED = 'rejected';6970const __PROTO__ = '__proto__';7172type RESPONSE_SYMBOL_TYPE = 'RESPONSE_SYMBOL'; // Fake symbol type.73const RESPONSE_SYMBOL: RESPONSE_SYMBOL_TYPE = Symbol() as any;7475type PendingChunk<T> = {76  status: 'pending',77  value: null | Array<InitializationReference | (T => mixed)>,78  reason: null | Array<InitializationReference | (mixed => mixed)>,79  then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,80};81type BlockedChunk<T> = {82  status: 'blocked',83  value: null | Array<InitializationReference | (T => mixed)>,84  reason: null | Array<InitializationReference | (mixed => mixed)>,85  then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,86};87type ResolvedModelChunk<T> = {88  status: 'resolved_model',89  value: string,90  reason: {id: number, [RESPONSE_SYMBOL_TYPE]: Response},91  then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,92};93type InitializedChunk<T> = {94  status: 'fulfilled',95  value: T,96  reason: null | NestedArrayContext,97  then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,98};99type InitializedStreamChunk<100  T: ReadableStream | $AsyncIterable<any, any, void>,101> = {102  status: 'fulfilled',103  value: T,104  reason: FlightStreamController,105  then(resolve: (ReadableStream) => mixed, reject?: (mixed) => mixed): void,106};107type ErroredChunk<T> = {108  status: 'rejected',109  value: null,110  reason: mixed,111  then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,112};113type SomeChunk<T> =114  | PendingChunk<T>115  | BlockedChunk<T>116  | ResolvedModelChunk<T>117  | InitializedChunk<T>118  | ErroredChunk<T>;119120// $FlowFixMe[missing-this-annot]121function ReactPromise(status: any, value: any, reason: any) {122  this.status = status;123  this.value = value;124  this.reason = reason;125}126// We subclass Promise.prototype so that we get other methods like .catch127ReactPromise.prototype = Object.create(Promise.prototype) as any;128// TODO: This doesn't return a new Promise chain unlike the real .then129ReactPromise.prototype.then = function <T>(130  this: SomeChunk<T>,131  resolve: (value: T) => mixed,132  reject: ?(reason: mixed) => mixed,133) {134  const chunk: SomeChunk<T> = this;135  // If we have resolved content, we try to initialize it first which136  // might put us back into one of the other states.137  switch (chunk.status) {138    case RESOLVED_MODEL:139      initializeModelChunk(chunk);140      break;141  }142  // The status might have changed after initialization.143  switch (chunk.status) {144    case INITIALIZED:145      if (typeof resolve === 'function') {146        let inspectedValue = chunk.value;147        // Recursively check if the value is itself a ReactPromise and if so if it points148        // back to itself. This helps catch recursive thenables early error.149        let cycleProtection = 0;150        const visited = new Set<typeof ReactPromise>();151        while (inspectedValue instanceof ReactPromise) {152          cycleProtection++;153          if (154            // $FlowFixMe[invalid-compare]155            inspectedValue === chunk ||156            visited.has(inspectedValue) ||157            cycleProtection > 1000158          ) {159            if (typeof reject === 'function') {160              reject(new Error('Cannot have cyclic thenables.'));161            }162            return;163          }164          visited.add(inspectedValue);165          // $FlowFixMe[invalid-compare]166          if (inspectedValue.status === INITIALIZED) {167            inspectedValue = inspectedValue.value;168          } else {169            // If this is lazily resolved, pending or blocked, it'll eventually become170            // initialized and break the loop. Rejected also breaks it.171            break;172          }173        }174        resolve(chunk.value);175      }176      break;177    case PENDING:178    case BLOCKED:179      if (typeof resolve === 'function') {180        if (chunk.value === null) {181          chunk.value = [] as Array<InitializationReference | (T => mixed)>;182        }183        chunk.value.push(resolve);184      }185      if (typeof reject === 'function') {186        if (chunk.reason === null) {187          chunk.reason = [] as Array<188            InitializationReference | (mixed => mixed),189          >;190        }191        chunk.reason.push(reject);192      }193      break;194    default:195      if (typeof reject === 'function') {196        reject(chunk.reason);197      }198      break;199  }200};201202const ObjectPrototype = Object.prototype;203const ArrayPrototype = Array.prototype;204205export type Response = {206  _bundlerConfig: ServerManifest,207  _prefix: string,208  _formData: BackingFormData,209  _chunks: Map<number, SomeChunk<any>>,210  _closed: boolean,211  _closedReason: mixed,212  _temporaryReferences: void | TemporaryReferenceSet,213  _rootArrayContexts: WeakMap<$ReadOnlyArray<mixed>, NestedArrayContext>,214  _arraySizeLimit: number,215};216217export function getRoot<T>(response: Response): Thenable<T> {218  const chunk = getChunk(response, 0);219  return chunk as any;220}221222function createPendingChunk<T>(response: Response): PendingChunk<T> {223  // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors224  return new ReactPromise(PENDING, null, null);225}226227function wakeChunk<T>(228  response: Response,229  listeners: Array<InitializationReference | (T => mixed)>,230  value: T,231  chunk: InitializedChunk<T>,232): void {233  for (let i = 0; i < listeners.length; i++) {234    const listener = listeners[i];235    if (typeof listener === 'function') {236      listener(value);237    } else {238      fulfillReference(response, listener, value, chunk.reason);239    }240  }241}242243function rejectChunk(244  response: Response,245  listeners: Array<InitializationReference | (mixed => mixed)>,246  error: mixed,247): void {248  for (let i = 0; i < listeners.length; i++) {249    const listener = listeners[i];250    if (typeof listener === 'function') {251      listener(error);252    } else {253      rejectReference(response, listener.handler, error);254    }255  }256}257258function wakeChunkIfInitialized<T>(259  response: Response,260  chunk: SomeChunk<T>,261  resolveListeners: Array<InitializationReference | (T => mixed)>,262  rejectListeners: null | Array<InitializationReference | (mixed => mixed)>,263): void {264  switch (chunk.status) {265    case INITIALIZED:266      wakeChunk(response, resolveListeners, chunk.value, chunk);267      break;268    case BLOCKED:269    case PENDING:270      if (chunk.value) {271        for (let i = 0; i < resolveListeners.length; i++) {272          chunk.value.push(resolveListeners[i]);273        }274      } else {275        chunk.value = resolveListeners;276      }277278      if (chunk.reason) {279        if (rejectListeners) {280          for (let i = 0; i < rejectListeners.length; i++) {281            chunk.reason.push(rejectListeners[i]);282          }283        }284      } else {285        chunk.reason = rejectListeners;286      }287      break;288    case ERRORED:289      if (rejectListeners) {290        rejectChunk(response, rejectListeners, chunk.reason);291      }292      break;293  }294}295296function triggerErrorOnChunk<T>(297  response: Response,298  chunk: SomeChunk<T>,299  error: mixed,300): void {301  if (chunk.status !== PENDING && chunk.status !== BLOCKED) {302    // If we get more data to an already resolved ID, we assume that it's303    // a stream chunk since any other row shouldn't have more than one entry.304    const streamChunk: InitializedStreamChunk<any> = chunk as any;305    const controller = streamChunk.reason;306    // $FlowFixMe[incompatible-type]: The error method should accept mixed.307    controller.error(error);308    return;309  }310  const listeners = chunk.reason;311  const erroredChunk: ErroredChunk<T> = chunk as any;312  erroredChunk.status = ERRORED;313  erroredChunk.reason = error;314  if (listeners !== null) {315    rejectChunk(response, listeners, error);316  }317}318319function createResolvedModelChunk<T>(320  response: Response,321  value: string,322  id: number,323): ResolvedModelChunk<T> {324  // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors325  return new ReactPromise(RESOLVED_MODEL, value, {326    id,327    [RESPONSE_SYMBOL]: response,328  });329}330331function createErroredChunk<T>(332  response: Response,333  reason: mixed,334): ErroredChunk<T> {335  // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors336  return new ReactPromise(ERRORED, null, reason);337}338339function resolveModelChunk<T>(340  response: Response,341  chunk: SomeChunk<T>,342  value: string,343  id: number,344): void {345  if (chunk.status !== PENDING) {346    // If we get more data to an already resolved ID, we assume that it's347    // a stream chunk since any other row shouldn't have more than one entry.348    const streamChunk: InitializedStreamChunk<any> = chunk as any;349    const controller = streamChunk.reason;350    if (value[0] === 'C') {351      controller.close(value === 'C' ? '"$undefined"' : value.slice(1));352    } else {353      controller.enqueueModel(value);354    }355    return;356  }357  const resolveListeners = chunk.value;358  const rejectListeners = chunk.reason;359  const resolvedChunk: ResolvedModelChunk<T> = chunk as any;360  resolvedChunk.status = RESOLVED_MODEL;361  resolvedChunk.value = value;362  resolvedChunk.reason = {id, [RESPONSE_SYMBOL]: response};363  if (resolveListeners !== null) {364    // This is unfortunate that we're reading this eagerly if365    // we already have listeners attached since they might no366    // longer be rendered or might not be the highest pri.367    initializeModelChunk(resolvedChunk);368    // The status might have changed after initialization.369    wakeChunkIfInitialized(response, chunk, resolveListeners, rejectListeners);370  }371}372373function createInitializedStreamChunk<374  T: ReadableStream | $AsyncIterable<any, any, void>,375>(376  response: Response,377  value: T,378  controller: FlightStreamController,379): InitializedChunk<T> {380  // We use the reason field to stash the controller since we already have that381  // field. It's a bit of a hack but efficient.382  // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors383  return new ReactPromise(INITIALIZED, value, controller);384}385386function createResolvedIteratorResultChunk<T>(387  response: Response,388  value: string,389  done: boolean,390): ResolvedModelChunk<IteratorResult<T, T>> {391  // To reuse code as much code as possible we add the wrapper element as part of the JSON.392  const iteratorResultJSON =393    (done ? '{"done":true,"value":' : '{"done":false,"value":') + value + '}';394  // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors395  return new ReactPromise(RESOLVED_MODEL, iteratorResultJSON, {396    id: -1,397    [RESPONSE_SYMBOL]: response,398  });399}400401function resolveIteratorResultChunk<T>(402  response: Response,403  chunk: SomeChunk<IteratorResult<T, T>>,404  value: string,405  done: boolean,406): void {407  // To reuse code as much code as possible we add the wrapper element as part of the JSON.408  const iteratorResultJSON =409    (done ? '{"done":true,"value":' : '{"done":false,"value":') + value + '}';410  resolveModelChunk(response, chunk, iteratorResultJSON, -1);411}412413function loadServerReference<A: Iterable<any>, T>(414  response: Response,415  metaData: {416    id: any,417    bound: null | Thenable<Array<any>>,418  },419  parentObject: Object,420  key: string,421): (...A) => Promise<T> {422  const id: ServerReferenceId = metaData.id;423  if (typeof id !== 'string') {424    return null as any;425  }426  if (key === 'then') {427    // This should never happen because we always serialize objects with then-functions428    // as "thenable" which reduces to ReactPromise with no other fields.429    return null as any;430  }431432  // Check for a cached promise from a previous call with the same metadata.433  // This handles deduplication when the same server reference appears multiple434  // times in the payload.435  const cachedPromise: SomeChunk<T> | void = (metaData as any).$$promise;436  if (cachedPromise !== undefined) {437    if (cachedPromise.status === INITIALIZED) {438      // The value was already resolved by a previous call.439      const resolvedValue: T = cachedPromise.value;440      if (key === __PROTO__) {441        return null as any;442      }443      parentObject[key] = resolvedValue;444      return resolvedValue as any;445    }446447    // The promise is still blocked. Increment the handler dependency count ...448    let handler: InitializationHandler;449    if (initializingHandler) {450      handler = initializingHandler;451      handler.deps++;452    } else {453      handler = initializingHandler = {454        chunk: null,455        value: null,456        reason: null,457        deps: 1,458        errored: false,459      };460    }461    // ... and register resolve and reject listeners on the promise.462    cachedPromise.then(463      resolveReference.bind(null, response, handler, parentObject, key),464      rejectReference.bind(null, response, handler),465    );466467    // Return a place holder value for now.468    return null as any;469  }470471  // This is the first call for this server reference metadata. Create a cached472  // promise to be used for subsequent calls.473  // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors474  const blockedPromise: BlockedChunk<T> = new ReactPromise(BLOCKED, null, null);475  (metaData as any).$$promise = blockedPromise;476477  const serverReference: ServerReference<T> =478    resolveServerReference<$FlowFixMe>(response._bundlerConfig, id);479  // We expect most servers to not really need this because you'd just have all480  // the relevant modules already loaded but it allows for lazy loading of code481  // if needed.482  const bound = metaData.bound;483  let serverReferencePromise: null | Thenable<any> =484    preloadModule(serverReference);485  if (!serverReferencePromise) {486    if (bound instanceof ReactPromise) {487      serverReferencePromise = Promise.resolve(bound);488    } else {489      const resolvedValue = requireModule(serverReference) as any;490      // Resolve the cached promise synchronously.491      const initializedPromise: InitializedChunk<T> = blockedPromise as any;492      initializedPromise.status = INITIALIZED;493      initializedPromise.value = resolvedValue;494      initializedPromise.reason = null;495      return resolvedValue;496    }497  } else if (bound instanceof ReactPromise) {498    serverReferencePromise = Promise.all([serverReferencePromise, bound]);499  }500501  let handler: InitializationHandler;502  if (initializingHandler) {503    handler = initializingHandler;504    handler.deps++;505  } else {506    handler = initializingHandler = {507      chunk: null,508      value: null,509      reason: null,510      deps: 1,511      errored: false,512    };513  }514515  function fulfill(): void {516    let resolvedValue = requireModule(serverReference) as any;517518    if (metaData.bound) {519      // This promise is coming from us and should have initialized by now.520      const promiseValue = (metaData.bound as any).value;521      const boundArgs: Array<any> = isArray(promiseValue)522        ? promiseValue.slice(0)523        : [];524      if (boundArgs.length > MAX_BOUND_ARGS) {525        reject(526          new Error(527            'Server Function has too many bound arguments. Received ' +528              boundArgs.length +529              ' but the limit is ' +530              MAX_BOUND_ARGS +531              '.',532          ),533        );534        return;535      }536      boundArgs.unshift(null); // this537      resolvedValue = resolvedValue.bind.apply(resolvedValue, boundArgs);538    }539540    // Resolve the cached promise so subsequent references can use the value.541    const resolveListeners = blockedPromise.value;542    const initializedPromise: InitializedChunk<T> = blockedPromise as any;543    initializedPromise.status = INITIALIZED;544    initializedPromise.value = resolvedValue;545    initializedPromise.reason = null;546    if (resolveListeners !== null) {547      // Notify any resolve listeners that were added via .then() from548      // subsequent loadServerReference calls for the same reference.549      wakeChunk(response, resolveListeners, resolvedValue, initializedPromise);550    }551552    resolveReference(response, handler, parentObject, key, resolvedValue);553  }554555  function reject(error: mixed): void {556    // Mark the cached promise as errored so subsequent references fail too.557    const rejectListeners = blockedPromise.reason;558    const erroredPromise: ErroredChunk<T> = blockedPromise as any;559    erroredPromise.status = ERRORED;560    erroredPromise.value = null;561    erroredPromise.reason = error;562    if (rejectListeners !== null) {563      // Notify any reject listeners that were added via .then() from subsequent564      // loadServerReference calls for the same reference.565      rejectChunk(response, rejectListeners, error);566    }567568    rejectReference(response, handler, error);569  }570571  serverReferencePromise.then(fulfill, reject);572573  // Return a place holder value for now.574  return null as any;575}576577function reviveModel(578  response: Response,579  parentObj: any,580  parentKey: string,581  value: JSONValue,582  reference: void | string,583  arrayRoot: null | NestedArrayContext,584): any {585  if (typeof value === 'string') {586    // We can't use .bind here because we need the "this" value.587    return parseModelString(588      response,589      parentObj,590      parentKey,591      value,592      reference,593      arrayRoot,594    );595  }596  if (typeof value === 'object' && value !== null) {597    if (598      reference !== undefined &&599      response._temporaryReferences !== undefined600    ) {601      // Store this object's reference in case it's returned later.602      registerTemporaryReference(603        response._temporaryReferences,604        value,605        reference,606      );607    }608    if (isArray(value)) {609      let childContext: NestedArrayContext;610      if (arrayRoot === null) {611        childContext = {612          count: 0,613          fork: false,614        } as NestedArrayContext;615        response._rootArrayContexts.set(value, childContext);616      } else {617        childContext = arrayRoot;618      }619      if (value.length > 1) {620        childContext.fork = true;621      }622      bumpArrayCount(623        childContext,624        // Number of commas + square brackets625        // value.length - 1 + 2626        value.length + 1,627        response,628      );629      for (let i = 0; i < value.length; i++) {630        const childRef =631          reference !== undefined ? reference + ':' + i : undefined;632        // $FlowFixMe[cannot-write]633        value[i] = reviveModel(634          response,635          value,636          '' + i,637          value[i],638          childRef,639          childContext,640        );641      }642    } else {643      for (const key in value) {644        if (hasOwnProperty.call(value, key)) {645          if (key === __PROTO__) {646            // $FlowFixMe[cannot-write]647            delete value[key];648            continue;649          }650          const childRef =651            reference !== undefined && key.indexOf(':') === -1652              ? reference + ':' + key653              : undefined;654          const newValue = reviveModel(655            response,656            value,657            key,658            value[key],659            childRef,660            null, // The array context resets when we're entering a non-array661          );662          if (newValue !== undefined) {663            // $FlowFixMe[cannot-write]664            value[key] = newValue;665          } else {666            // $FlowFixMe[cannot-write]667            delete value[key];668          }669        }670      }671    }672  }673  return value;674}675676type NestedArrayContext = {677  // Keeps track of how many slots, bytes or characters are in nested arrays/strings/typed arrays.678  count: number,679  // A single child is itself not harmful. There needs to be at least one parent array with more680  // than one child.681  fork: boolean,682};683684function bumpArrayCount(685  arrayContext: NestedArrayContext,686  slots: number,687  response: Response,688): void {689  const newCount = (arrayContext.count += slots);690  if (newCount > response._arraySizeLimit && arrayContext.fork) {691    throw new Error(692      'Maximum array nesting exceeded. Large nested arrays can be dangerous. Try adding intermediate objects.',693    );694  }695}696697type InitializationReference = {698  handler: InitializationHandler,699  parentObject: Object,700  key: string,701  map: (702    response: Response,703    model: any,704    parentObject: Object,705    key: string,706  ) => any,707  path: Array<string>,708  arrayRoot: null | NestedArrayContext,709};710type InitializationHandler = {711  chunk: null | BlockedChunk<any>,712  value: any,713  // TODO: Split type to make it impossible to treat a thrown value as NestedArrayContext.714  // thrown value if errored, otherwise array context715  reason: mixed | NestedArrayContext,716  deps: number,717  errored: boolean,718};719let initializingHandler: null | InitializationHandler = null;720721function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {722  const prevHandler = initializingHandler;723  initializingHandler = null;724725  const {[RESPONSE_SYMBOL]: response, id} = chunk.reason;726727  const rootReference = id === -1 ? undefined : id.toString(16);728729  const resolvedModel = chunk.value;730731  // We go to the BLOCKED state until we've fully resolved this.732  // We do this before parsing in case we try to initialize the same chunk733  // while parsing the model. Such as in a cyclic reference.734  const cyclicChunk: BlockedChunk<T> = chunk as any;735  cyclicChunk.status = BLOCKED;736  cyclicChunk.value = null;737  cyclicChunk.reason = null;738739  try {740    const rawModel = JSON.parse(resolvedModel);741742    // The root might not be an array but if it is we want to track the count of entries.743    const arrayRoot: NestedArrayContext = {744      count: 0,745      fork: false,746    };747748    const value: T = reviveModel(749      response,750      {'': rawModel},751      '',752      rawModel,753      rootReference,754      arrayRoot,755    );756757    // Invoke any listeners added while resolving this model. I.e. cyclic758    // references. This may or may not fully resolve the model depending on759    // if they were blocked.760    const resolveListeners = cyclicChunk.value;761    if (resolveListeners !== null) {762      cyclicChunk.value = null;763      cyclicChunk.reason = null;764      for (let i = 0; i < resolveListeners.length; i++) {765        const listener = resolveListeners[i];766        if (typeof listener === 'function') {767          listener(value);768        } else {769          fulfillReference(response, listener, value, arrayRoot);770        }771      }772    }773    if (initializingHandler !== null) {774      if (initializingHandler.errored) {775        throw initializingHandler.reason;776      }777      if (initializingHandler.deps > 0) {778        // We discovered new dependencies on modules that are not yet resolved.779        // We have to keep the BLOCKED state until they're resolved.780        initializingHandler.value = value;781        initializingHandler.reason = arrayRoot;782        initializingHandler.chunk = cyclicChunk;783        return;784      }785    }786    const initializedChunk: InitializedChunk<T> = chunk as any;787    initializedChunk.status = INITIALIZED;788    initializedChunk.value = value;789    initializedChunk.reason = arrayRoot;790  } catch (error) {791    const erroredChunk: ErroredChunk<T> = chunk as any;792    erroredChunk.status = ERRORED;793    erroredChunk.reason = error;794  } finally {795    initializingHandler = prevHandler;796  }797}798799// Report that any missing chunks in the model is now going to throw this800// error upon read. Also notify any pending promises.801export function reportGlobalError(response: Response, error: Error): void {802  response._closed = true;803  response._closedReason = error;804  response._chunks.forEach(chunk => {805    // If this chunk was already resolved or errored, it won't806    // trigger an error but if it wasn't then we need to807    // because we won't be getting any new data to resolve it.808    if (chunk.status === PENDING) {809      triggerErrorOnChunk(response, chunk, error);810    } else if (chunk.status === INITIALIZED) {811      const initializedChunk:812        | InitializedChunk<any>813        | InitializedStreamChunk<any> = chunk as any;814      if (initializedChunk.reason !== null) {815        const maybeController = initializedChunk.reason;816        // $FlowFixMe[method-unbinding] Just doing a typeof check817        if (typeof maybeController.error === 'function') {818          maybeController.error(error);819        }820      }821    }822  });823}824825function getChunk(response: Response, id: number): SomeChunk<any> {826  const chunks = response._chunks;827  let chunk = chunks.get(id);828  if (!chunk) {829    const prefix = response._prefix;830    const key = prefix + id;831    // Check if we have this field in the backing store already.832    const backingEntry = getBackingEntry(response._formData, key);833    if (typeof backingEntry === 'string') {834      chunk = createResolvedModelChunk(response, backingEntry, id);835    } else if (response._closed) {836      // We have already errored the response and we're not going to get837      // anything more streaming in so this will immediately error.838      chunk = createErroredChunk(response, response._closedReason);839    } else {840      // We're still waiting on this entry to stream in.841      chunk = createPendingChunk(response);842    }843    chunks.set(id, chunk);844  }845  return chunk;846}847848function fulfillReference(849  response: Response,850  reference: InitializationReference,851  value: any,852  arrayRoot: null | NestedArrayContext,853): void {854  const {handler, parentObject, key, map, path} = reference;855856  let resolvedValue;857  try {858    let localLength: number = 0;859    const rootArrayContexts = response._rootArrayContexts;860    for (let i = 1; i < path.length; i++) {861      // The server doesn't have any lazy references so we don't expect to go through a Promise.862      const name = path[i];863      if (864        typeof value === 'object' &&865        value !== null &&866        (getPrototypeOf(value) === ObjectPrototype ||867          getPrototypeOf(value) === ArrayPrototype) &&868        hasOwnProperty.call(value, name)869      ) {870        value = value[name];871        if (isArray(value)) {872          localLength = 0;873          arrayRoot = rootArrayContexts.get(value) || arrayRoot;874        } else {875          arrayRoot = null;876          if (typeof value === 'string') {877            localLength = value.length;878          } else if (typeof value === 'bigint') {879            // Estimate the length to avoid expensive toString() calls on large880            // BigInt values. If the value is too large, we get Infinity, which881            // will trigger the array size limit error.882            // eslint-disable-next-line react-internal/no-primitive-constructors883            const n = Math.abs(Number(value));884            if (n === 0) {885              localLength = 1;886            } else {887              localLength = Math.floor(Math.log10(n)) + 1;888            }889          } else if (ArrayBuffer.isView(value)) {890            localLength = value.byteLength;891          } else {892            localLength = 0;893          }894        }895      } else {896        throw new Error('Invalid reference.');897      }898    }899900    resolvedValue = map(response, value, parentObject, key);901902    // Add any array counts to the reference's array root. The value that we're903    // resolving might have deep nesting that we need to resolve.904    const referenceArrayRoot = reference.arrayRoot;905    if (referenceArrayRoot !== null) {906      if (arrayRoot !== null) {907        if (arrayRoot.fork) {908          referenceArrayRoot.fork = true;909        }910        bumpArrayCount(referenceArrayRoot, arrayRoot.count, response);911      } else if (localLength > 0) {912        bumpArrayCount(referenceArrayRoot, localLength, response);913      }914    }915  } catch (error) {916    rejectReference(response, handler, error);917    return;918  }919920  // There are no Elements or Debug Info to transfer here.921922  resolveReference(response, handler, parentObject, key, resolvedValue);923}924925function resolveReference(926  response: Response,927  handler: InitializationHandler,928  parentObject: Object,929  key: string,930  resolvedValue: mixed,931): void {932  if (key !== __PROTO__) {933    parentObject[key] = resolvedValue;934  }935936  // If this is the root object for a model reference, where `handler.value`937  // is a stale `null`, the resolved value can be used directly.938  if (key === '' && handler.value === null) {939    handler.value = resolvedValue;940  }941942  handler.deps--;943944  if (handler.deps === 0) {945    const chunk = handler.chunk;946    if (chunk === null || chunk.status !== BLOCKED) {947      return;948    }949    const resolveListeners = chunk.value;950    const initializedChunk: InitializedChunk<any> = chunk as any;951    initializedChunk.status = INITIALIZED;952    initializedChunk.value = handler.value;953    initializedChunk.reason =954      // $FlowFixMe[incompatible-type] Assuming handler.errored is false.955      handler.reason;956    if (resolveListeners !== null) {957      wakeChunk(response, resolveListeners, handler.value, initializedChunk);958    }959  }960}961962function rejectReference(963  response: Response,964  handler: InitializationHandler,965  error: mixed,966): void {967  if (handler.errored) {968    // We've already errored. We could instead build up an AggregateError969    // but if there are multiple errors we just take the first one like970    // Promise.all.971    return;972  }973  handler.errored = true;974  handler.value = null;975  handler.reason = error;976  const chunk = handler.chunk;977  if (chunk === null || chunk.status !== BLOCKED) {978    return;979  }980  // There's no debug info to forward in this direction.981  triggerErrorOnChunk(response, chunk, error);982}983984function waitForReference<T>(985  response: Response,986  referencedChunk: BlockedChunk<T>,987  parentObject: Object,988  key: string,989  arrayRoot: null | NestedArrayContext,990  map: (response: Response, model: any, parentObject: Object, key: string) => T,991  path: Array<string>,992): T {993  let handler: InitializationHandler;994  if (initializingHandler) {995    handler = initializingHandler;996    handler.deps++;997  } else {998    handler = initializingHandler = {999      chunk: null,1000      value: null,1001      reason: null,1002      deps: 1,1003      errored: false,1004    };1005  }10061007  const reference: InitializationReference = {1008    handler,1009    parentObject,1010    key,1011    map,1012    path,1013    arrayRoot,1014  };10151016  // Add "listener".1017  if (referencedChunk.value === null) {1018    referencedChunk.value = [reference];1019  } else {1020    referencedChunk.value.push(reference);1021  }1022  if (referencedChunk.reason === null) {1023    referencedChunk.reason = [reference];1024  } else {1025    referencedChunk.reason.push(reference);1026  }10271028  // Return a place holder value for now.1029  return null as any;1030}10311032function getOutlinedModel<T>(1033  response: Response,1034  reference: string,1035  parentObject: Object,1036  key: string,1037  referenceArrayRoot: null | NestedArrayContext,1038  map: (response: Response, model: any, parentObject: Object, key: string) => T,1039): T {1040  const path = reference.split(':');1041  const id = parseInt(path[0], 16);1042  let chunk = getChunk(response, id);1043  switch (chunk.status) {1044    case RESOLVED_MODEL:1045      initializeModelChunk(chunk);1046      // $FlowFixMe[incompatible-type] We just initialized this chunk so it can't be a ResolvedModelChunk anymore.1047      chunk = chunk as Exclude<SomeChunk<T>, ResolvedModelChunk<T>>;1048      break;1049  }1050  // The status might have changed after initialization.1051  switch (chunk.status) {1052    case INITIALIZED:1053      let value = chunk.value;1054      const arrayRootOrController:1055        | null1056        | NestedArrayContext1057        | FlightStreamController = chunk.reason;1058      if (arrayRootOrController !== null && 'error' in arrayRootOrController) {1059        throw new Error(1060          'Expected an initialized chunk but got an initialized stream chunk instead. ' +1061            'This payload may have been submitted by an older version of React.',1062        );1063      }1064      let arrayRoot = arrayRootOrController;10651066      let localLength: number = 0;1067      const rootArrayContexts = response._rootArrayContexts;1068      for (let i = 1; i < path.length; i++) {1069        const name = path[i];1070        if (1071          typeof value === 'object' &&1072          // $FlowFixMe[invalid-compare] This check is still needed at runtime.1073          value !== null &&1074          (getPrototypeOf(value) === ObjectPrototype ||1075            getPrototypeOf(value) === ArrayPrototype) &&1076          hasOwnProperty.call(value, name)1077        ) {1078          value = value[name];1079          if (isArray(value)) {1080            localLength = 0;1081            arrayRoot =1082              rootArrayContexts.get(1083                // $FlowFixMe[incompatible-type] Our `isArray` typing can't narrow `mixed`1084                value as $ReadOnlyArray<mixed>,1085              ) || arrayRoot;1086          } else {1087            arrayRoot = null;1088            if (typeof value === 'string') {1089              localLength = value.length;1090            } else if (typeof value === 'bigint') {1091              // Estimate the length to avoid expensive toString() calls on large1092              // BigInt values. If the value is too large, we get Infinity, which1093              // will trigger the array size limit error.1094              // eslint-disable-next-line react-internal/no-primitive-constructors1095              const n = Math.abs(Number(value));1096              if (n === 0) {1097                localLength = 1;1098              } else {1099                localLength = Math.floor(Math.log10(n)) + 1;1100              }1101            } else if (ArrayBuffer.isView(value)) {1102              localLength = value.byteLength;1103            } else {1104              localLength = 0;1105            }1106          }1107        } else {1108          throw new Error('Invalid reference.');1109        }1110      }1111      const chunkValue = map(response, value, parentObject, key);11121113      // Add any array counts to the reference's array root. The value that we're1114      // resolving might have deep nesting that we need to resolve.1115      if (referenceArrayRoot !== null) {1116        if (arrayRoot !== null) {1117          if (arrayRoot.fork) {1118            referenceArrayRoot.fork = true;1119          }1120          bumpArrayCount(referenceArrayRoot, arrayRoot.count, response);1121        } else if (localLength > 0) {1122          bumpArrayCount(referenceArrayRoot, localLength, response);1123        }1124      }1125      // There's no Element nor Debug Info in the ReplyServer so we don't have to check those here.1126      return chunkValue;1127    case BLOCKED:1128      return waitForReference(1129        response,1130        chunk,1131        parentObject,1132        key,1133        referenceArrayRoot,1134        map,1135        path,1136      );1137    case PENDING:1138      // If we don't have the referenced chunk yet, then this must be a forward reference,1139      // which is not allowed.1140      throw new Error('Invalid forward reference.');1141    default:1142      // This is an error. Instead of erroring directly, we're going to encode this on1143      // an initialization handler.1144      if (initializingHandler) {1145        initializingHandler.errored = true;1146        initializingHandler.value = null;1147        initializingHandler.reason = chunk.reason;1148      } else {1149        initializingHandler = {1150          chunk: null,1151          value: null,1152          reason: chunk.reason,1153          deps: 0,1154          errored: true,1155        };1156      }1157      // Placeholder1158      return null as any;1159  }1160}11611162function createMap(1163  response: Response,1164  model: Array<[any, any]>,1165): Map<any, any> {1166  if (!isArray(model)) {1167    throw new Error('Invalid Map initializer.');1168  }1169  if ((model as any).$$consumed === true) {1170    throw new Error('Already initialized Map.');1171  }1172  // This needs to come first to prevent the model from being consumed again in case of a cyclic reference.1173  (model as any).$$consumed = true;1174  const map = new Map(model);1175  return map;1176}11771178function createSet(response: Response, model: Array<any>): Set<any> {1179  if (!isArray(model)) {1180    throw new Error('Invalid Set initializer.');1181  }1182  if ((model as any).$$consumed === true) {1183    throw new Error('Already initialized Set.');1184  }1185  // This needs to come first to prevent the model from being consumed again in case of a cyclic reference.1186  (model as any).$$consumed = true;1187  const set = new Set(model);1188  return set;1189}11901191function extractIterator(response: Response, model: Array<any>): Iterator<any> {1192  if (!isArray(model)) {1193    throw new Error('Invalid Iterator initializer.');1194  }1195  if ((model as any).$$consumed === true) {1196    throw new Error('Already initialized Iterator.');1197  }1198  // This needs to come first to prevent the model from being consumed again in case of a cyclic reference.1199  (model as any).$$consumed = true;1200  // $FlowFixMe[incompatible-use]: This uses raw Symbols because we're extracting from a native array.1201  const iterator = model[Symbol.iterator]();1202  return iterator;1203}12041205function createModel(1206  response: Response,1207  model: any,1208  parentObject: Object,1209  key: string,1210): any {1211  if (key === 'then' && typeof model === 'function') {1212    // This should never happen because we always serialize objects with then-functions1213    // as "thenable" which reduces to ReactPromise with no other fields.1214    return null;1215  }1216  return model;1217}12181219function parseTypedArray<T: $ArrayBufferView | ArrayBuffer>(1220  response: Response,1221  reference: string,1222  constructor: any,1223  bytesPerElement: number,1224  parentObject: Object,1225  parentKey: string,1226  referenceArrayRoot: null | NestedArrayContext,1227): null {1228  const id = parseInt(reference.slice(2), 16);1229  const prefix = response._prefix;1230  const key = prefix + id;1231  const chunks = response._chunks;1232  if (chunks.has(id)) {1233    throw new Error('Already initialized typed array.');1234  }1235  chunks.set(1236    id,1237    // We don't need to put the actual Blob in the chunk,1238    // because it shouldn't be accessed by anything else.1239    createErroredChunk(response, new Error('Already initialized typed array.')),1240  );12411242  // We should have this backingEntry in the store already because we emitted1243  // it before referencing it. It should be a Blob.1244  const backingEntry: Blob = getBackingEntry(response._formData, key) as any;12451246  const promise: Promise<ArrayBuffer> = backingEntry.arrayBuffer();12471248  // Since loading the buffer is an async operation we'll be blocking the parent1249  // chunk.12501251  let handler: InitializationHandler;1252  if (initializingHandler) {1253    handler = initializingHandler;1254    handler.deps++;1255  } else {1256    handler = initializingHandler = {1257      chunk: null,1258      value: null,1259      reason: null,1260      deps: 1,1261      errored: false,1262    };1263  }12641265  function fulfill(buffer: ArrayBuffer): void {1266    try {1267      if (referenceArrayRoot !== null) {1268        bumpArrayCount(referenceArrayRoot, buffer.byteLength, response);1269      }12701271      const resolvedValue: T =1272        constructor === ArrayBuffer1273          ? (buffer as any)1274          : (new constructor(buffer) as any);12751276      if (key !== __PROTO__) {1277        parentObject[parentKey] = resolvedValue;1278      }12791280      // If this is the root object for a model reference, where `handler.value`1281      // is a stale `null`, the resolved value can be used directly.1282      if (parentKey === '' && handler.value === null) {1283        handler.value = resolvedValue;1284      }1285    } catch (x) {1286      reject(x);1287      return;1288    }12891290    handler.deps--;12911292    if (handler.deps === 0) {1293      const chunk = handler.chunk;1294      if (chunk === null || chunk.status !== BLOCKED) {1295        return;1296      }1297      const resolveListeners = chunk.value;1298      const initializedChunk: InitializedChunk<T> = chunk as any;1299      initializedChunk.status = INITIALIZED;1300      initializedChunk.value = handler.value;1301      // We don't keep an array count for this since it won't be referenced again.1302      // In fact, we don't really need to store this chunk at all.1303      initializedChunk.reason = null;1304      if (resolveListeners !== null) {1305        wakeChunk(response, resolveListeners, handler.value, initializedChunk);1306      }1307    }1308  }13091310  function reject(error: mixed): void {1311    if (handler.errored) {1312      // We've already errored. We could instead build up an AggregateError1313      // but if there are multiple errors we just take the first one like1314      // Promise.all.1315      return;1316    }1317    handler.errored = true;1318    handler.value = null;1319    handler.reason = error;1320    const chunk = handler.chunk;1321    if (chunk === null || chunk.status !== BLOCKED) {1322      return;1323    }1324    triggerErrorOnChunk(response, chunk, error);1325  }13261327  promise.then(fulfill, reject);13281329  return null;1330}13311332function resolveStream<T: ReadableStream | $AsyncIterable<any, any, void>>(1333  response: Response,1334  id: number,1335  stream: T,1336  controller: FlightStreamController,1337): void {1338  const chunks = response._chunks;1339  const chunk = createInitializedStreamChunk(response, stream, controller);1340  chunks.set(id, chunk);13411342  const prefix = response._prefix;1343  const key = prefix + id;1344  const existingEntries = getAllBackingEntries(response._formData, key);1345  for (let i = 0; i < existingEntries.length; i++) {1346    const value = existingEntries[i];1347    if (typeof value === 'string') {1348      if (value[0] === 'C') {1349        controller.close(value === 'C' ? '"$undefined"' : value.slice(1));1350      } else {1351        controller.enqueueModel(value);1352      }1353    }1354  }1355}13561357function parseReadableStream<T>(1358  response: Response,1359  reference: string,1360  type: void | 'bytes',1361  parentObject: Object,1362  parentKey: string,1363): ReadableStream {1364  const id = parseInt(reference.slice(2), 16);1365  const chunks = response._chunks;1366  if (chunks.has(id)) {1367    throw new Error('Already initialized stream.');1368  }13691370  let controller: ReadableStreamController = null as any;1371  let closed = false;1372  const stream = new ReadableStream({1373    type: type,1374    start(c) {1375      controller = c;1376    },1377  });1378  let previousBlockedChunk: SomeChunk<T> | null = null;1379  function enqueue(value: T): void {1380    if (type === 'bytes' && !ArrayBuffer.isView(value)) {1381      flightController.error(new Error('Invalid data for bytes stream.'));1382      return;1383    }1384    controller.enqueue(value);1385  }1386  const flightController = {1387    enqueueModel(json: string): void {1388      if (previousBlockedChunk === null) {1389        // If we're not blocked on any other chunks, we can try to eagerly initialize1390        // this as a fast-path to avoid awaiting them.1391        const chunk: ResolvedModelChunk<T> = createResolvedModelChunk(1392          response,1393          json,1394          -1,1395        );1396        initializeModelChunk(chunk);1397        const initializedChunk: SomeChunk<T> = chunk;1398        if (initializedChunk.status === INITIALIZED) {1399          enqueue(initializedChunk.value);1400        } else {1401          chunk.then(enqueue, flightController.error);1402          previousBlockedChunk = chunk;1403        }1404      } else {1405        // We're still waiting on a previous chunk so we can't enqueue quite yet.1406        const blockedChunk = previousBlockedChunk;1407        const chunk: SomeChunk<T> = createPendingChunk(response);1408        chunk.then(enqueue, flightController.error);1409        previousBlockedChunk = chunk;1410        blockedChunk.then(function () {1411          if (previousBlockedChunk === chunk) {1412            // We were still the last chunk so we can now clear the queue and return1413            // to synchronous emitting.1414            previousBlockedChunk = null;1415          }1416          resolveModelChunk(response, chunk, json, -1);1417        });1418      }1419    },1420    close(json: string): void {1421      if (closed) {1422        return;1423      }1424      closed = true;1425      if (previousBlockedChunk === null) {1426        controller.close();1427      } else {1428        const blockedChunk = previousBlockedChunk;1429        // We shouldn't get any more enqueues after this so we can set it back to null.1430        previousBlockedChunk = null;1431        blockedChunk.then(() => controller.close());1432      }1433    },1434    error(error: mixed): void {1435      if (closed) {1436        return;1437      }1438      closed = true;1439      if (previousBlockedChunk === null) {1440        // $FlowFixMe[incompatible-type]1441        controller.error(error);1442      } else {1443        const blockedChunk = previousBlockedChunk;1444        // We shouldn't get any more enqueues after this so we can set it back to null.1445        previousBlockedChunk = null;1446        blockedChunk.then(() => controller.error(error as any));1447      }1448    },1449  };1450  resolveStream(response, id, stream, flightController);1451  return stream;1452}14531454function FlightIterator(1455  this: {next: (arg: void) => SomeChunk<IteratorResult<any, any>>, ...},1456  next: (arg: void) => SomeChunk<IteratorResult<any, any>>,1457) {1458  this.next = next;1459  // TODO: Add return/throw as options for aborting.1460}1461// TODO: The iterator could inherit the AsyncIterator prototype which is not exposed as1462// a global but exists as a prototype of an AsyncGenerator. However, it's not needed1463// to satisfy the iterable protocol.1464FlightIterator.prototype = {} as any;1465FlightIterator.prototype[ASYNC_ITERATOR] = function asyncIterator(1466  this: $AsyncIterator<any, any, void>,1467) {1468  // Self referencing iterator.1469  return this;1470};14711472function parseAsyncIterable<T>(1473  response: Response,1474  reference: string,1475  iterator: boolean,1476  parentObject: Object,1477  parentKey: string,1478): $AsyncIterable<T, T, void> | $AsyncIterator<T, T, void> {1479  const id = parseInt(reference.slice(2), 16);1480  const chunks = response._chunks;1481  if (chunks.has(id)) {1482    throw new Error('Already initialized stream.');1483  }14841485  const buffer: Array<SomeChunk<IteratorResult<T, T>>> = [];1486  let closed = false;1487  let nextWriteIndex = 0;1488  const flightController = {1489    enqueueModel(value: string): void {1490      if (nextWriteIndex === buffer.length) {1491        buffer[nextWriteIndex] = createResolvedIteratorResultChunk(1492          response,1493          value,1494          false,1495        );1496      } else {1497        resolveIteratorResultChunk(1498          response,1499          buffer[nextWriteIndex],1500          value,1501          false,1502        );1503      }1504      nextWriteIndex++;1505    },1506    close(value: string): void {1507      if (closed) {1508        return;1509      }1510      closed = true;1511      if (nextWriteIndex === buffer.length) {1512        buffer[nextWriteIndex] = createResolvedIteratorResultChunk(1513          response,1514          value,1515          true,1516        );1517      } else {1518        resolveIteratorResultChunk(1519          response,1520          buffer[nextWriteIndex],1521          value,1522          true,1523        );1524      }1525      nextWriteIndex++;1526      while (nextWriteIndex < buffer.length) {1527        // In generators, any extra reads from the iterator have the value undefined.1528        resolveIteratorResultChunk(1529          response,1530          buffer[nextWriteIndex++],1531          '"$undefined"',1532          true,1533        );1534      }1535    },1536    error(error: Error): void {1537      if (closed) {1538        return;1539      }1540      closed = true;1541      if (nextWriteIndex === buffer.length) {1542        buffer[nextWriteIndex] =1543          createPendingChunk<IteratorResult<T, T>>(response);1544      }1545      while (nextWriteIndex < buffer.length) {1546        triggerErrorOnChunk(response, buffer[nextWriteIndex++], error);1547      }1548    },1549  };1550  const iterable: $AsyncIterable<T, T, void> = {1551    [ASYNC_ITERATOR](): $AsyncIterator<T, T, void> {1552      let nextReadIndex = 0;1553      // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors1554      return new FlightIterator((arg: void) => {1555        if (arg !== undefined) {1556          throw new Error(1557            'Values cannot be passed to next() of AsyncIterables passed to Client Components.',1558          );1559        }1560        if (nextReadIndex === buffer.length) {1561          if (closed) {1562            // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors1563            return new ReactPromise(1564              INITIALIZED,1565              {done: true, value: undefined},1566              null,1567            );1568          }1569          buffer[nextReadIndex] =1570            createPendingChunk<IteratorResult<T, T>>(response);1571        }1572        return buffer[nextReadIndex++];1573      });1574    },1575  };1576  // TODO: If it's a single shot iterator we can optimize memory by cleaning up the buffer after1577  // reading through the end, but currently we favor code size over this optimization.1578  const stream = iterator ? iterable[ASYNC_ITERATOR]() : iterable;1579  resolveStream(response, id, stream, flightController);1580  return stream;1581}15821583function parseModelString(1584  response: Response,1585  obj: Object,1586  key: string,1587  value: string,1588  reference: void | string,1589  arrayRoot: null | NestedArrayContext,1590): any {1591  if (value[0] === '$') {1592    switch (value[1]) {1593      case '$': {1594        // This was an escaped string value.1595        if (arrayRoot !== null) {1596          bumpArrayCount(arrayRoot, value.length - 1, response);1597        }1598        return value.slice(1);1599      }1600      case '@': {1601        // Promise1602        const id = parseInt(value.slice(2), 16);1603        const chunk = getChunk(response, id);1604        return chunk;1605      }1606      case 'h': {1607        // Server Reference1608        const ref = value.slice(2);1609        return getOutlinedModel(1610          response,1611          ref,1612          obj,1613          key,1614          null,1615          loadServerReference,1616        );1617      }1618      case 'T': {1619        // Temporary Reference1620        if (1621          reference === undefined ||1622          response._temporaryReferences === undefined1623        ) {1624          throw new Error(1625            'Could not reference an opaque temporary reference. ' +1626              'This is likely due to misconfiguring the temporaryReferences options ' +1627              'on the server.',1628          );1629        }1630        return createTemporaryReference(1631          response._temporaryReferences,1632          reference,1633        );1634      }1635      case 'Q': {1636        // Map1637        const ref = value.slice(2);1638        return getOutlinedModel(response, ref, obj, key, null, createMap);1639      }1640      case 'W': {1641        // Set1642        const ref = value.slice(2);1643        return getOutlinedModel(response, ref, obj, key, null, createSet);1644      }1645      case 'K': {1646        // FormData1647        const stringId = value.slice(2);16481649        const responsePrefix = response._prefix;1650        // Use the special marker from the Client to distinguish keys that should1651        // be consumed by referenced FormData.1652        const anyFormPrefix = responsePrefix + '_';1653        const formPrefix = anyFormPrefix + stringId + '_';16541655        const data = new FormData();1656        const backingFormData = response._formData;1657        // We're still transpiling for-of loops, so we have to use the iterator directly instead of a for-of loop.1658        while (true) {1659          const formDataKey = peekBackingEntry(backingFormData);1660          if (formDataKey === undefined) {1661            break;1662          }1663          if (formDataKey.startsWith(formPrefix)) {1664            const referencedFormDataValue = getAllBackingEntries(1665              backingFormData,1666              formDataKey,1667            );1668            const referencedFormDataKey = formDataKey.slice(formPrefix.length);1669            for (let i = 0; i < referencedFormDataValue.length; i++) {1670              // $FlowFixMe[incompatible-type]1671              data.append(referencedFormDataKey, referencedFormDataValue[i]);1672            }1673            consumeBackingEntry(backingFormData, formDataKey);1674          } else if (formDataKey.startsWith(anyFormPrefix)) {1675            // The FormData values are continuous and before the FormData reference.1676            // If we see something that doesn't look like a value for a referenced1677            // FormData, we can assume we're past the values for this FormData1678            // reference and stop iterating.1679            break;1680          } else {1681            // Either an outlined value or something not owned by this Reply.1682            advanceBackingEntryIterator(backingFormData);1683          }1684        }1685        return data;1686      }1687      case 'i': {1688        // Iterator1689        const ref = value.slice(2);1690        return getOutlinedModel(response, ref, obj, key, null, extractIterator);1691      }1692      case 'I': {1693        // $Infinity1694        return Infinity;1695      }1696      case '-': {1697        // $-0 or $-Infinity1698        if (value === '$-0') {1699          return -0;1700        } else {1701          return -Infinity;1702        }1703      }1704      case 'N': {1705        // $NaN1706        return NaN;1707      }1708      case 'u': {1709        // matches "$undefined"1710        // Special encoding for `undefined` which can't be serialized as JSON otherwise.1711        return undefined;1712      }1713      case 'D': {1714        // Date1715        return new Date(Date.parse(value.slice(2)));1716      }1717      case 'n': {1718        // BigInt1719        const bigIntStr = value.slice(2);1720        if (bigIntStr.length > MAX_BIGINT_DIGITS) {1721          throw new Error(1722            'BigInt is too large. Received ' +1723              bigIntStr.length +1724              ' digits but the limit is ' +1725              MAX_BIGINT_DIGITS +1726              '.',1727          );1728        }1729        if (arrayRoot !== null) {1730          bumpArrayCount(arrayRoot, bigIntStr.length, response);1731        }1732        return BigInt(bigIntStr);1733      }1734      case 'A':1735        return parseTypedArray(1736          response,1737          value,1738          ArrayBuffer,1739          1,1740          obj,1741          key,1742          arrayRoot,1743        );1744      case 'O':1745        return parseTypedArray(1746          response,1747          value,1748          Int8Array,1749          1,1750          obj,1751          key,1752          arrayRoot,1753        );1754      case 'o':1755        return parseTypedArray(1756          response,1757          value,1758          Uint8Array,1759          1,1760          obj,1761          key,1762          arrayRoot,1763        );1764      case 'U':1765        return parseTypedArray(1766          response,1767          value,1768          Uint8ClampedArray,1769          1,1770          obj,1771          key,1772          arrayRoot,1773        );1774      case 'S':1775        return parseTypedArray(1776          response,1777          value,1778          Int16Array,1779          2,1780          obj,1781          key,1782          arrayRoot,1783        );1784      case 's':1785        return parseTypedArray(1786          response,1787          value,1788          Uint16Array,1789          2,1790          obj,1791          key,1792          arrayRoot,1793        );1794      case 'L':1795        return parseTypedArray(1796          response,1797          value,1798          Int32Array,1799          4,1800          obj,1801          key,1802          arrayRoot,1803        );1804      case 'l':1805        return parseTypedArray(1806          response,1807          value,1808          Uint32Array,1809          4,1810          obj,1811          key,1812          arrayRoot,1813        );1814      case 'G':1815        return parseTypedArray(1816          response,1817          value,1818          Float32Array,1819          4,1820          obj,1821          key,1822          arrayRoot,1823        );1824      case 'g':1825        return parseTypedArray(1826          response,1827          value,1828          Float64Array,1829          8,1830          obj,1831          key,1832          arrayRoot,1833        );1834      case 'M':1835        return parseTypedArray(1836          response,1837          value,1838          BigInt64Array,1839          8,1840          obj,1841          key,1842          arrayRoot,1843        );1844      case 'm':1845        return parseTypedArray(1846          response,1847          value,1848          BigUint64Array,1849          8,1850          obj,1851          key,1852          arrayRoot,1853        );1854      case 'V':1855        return parseTypedArray(1856          response,1857          value,1858          DataView,1859          1,1860          obj,1861          key,1862          arrayRoot,1863        );1864      case 'B': {1865        // Blob1866        const id = parseInt(value.slice(2), 16);1867        const prefix = response._prefix;1868        const blobKey = prefix + id;1869        // We should have this backingEntry in the store already because we emitted1870        // it before referencing it. It should be a Blob.1871        const backingEntry: Blob = getBackingEntry(1872          response._formData,1873          blobKey,1874        ) as any;1875        if (!(backingEntry instanceof Blob)) {1876          throw new Error('Referenced Blob is not a Blob.');1877        }1878        return backingEntry;1879      }1880      case 'R': {1881        return parseReadableStream(response, value, undefined, obj, key);1882      }1883      case 'r': {1884        return parseReadableStream(response, value, 'bytes', obj, key);1885      }1886      case 'X': {1887        return parseAsyncIterable(response, value, false, obj, key);1888      }1889      case 'x': {1890        return parseAsyncIterable(response, value, true, obj, key);1891      }1892    }1893    // We assume that anything else is a reference ID.1894    const ref = value.slice(1);1895    return getOutlinedModel(response, ref, obj, key, arrayRoot, createModel);1896  }1897  if (arrayRoot !== null) {1898    bumpArrayCount(arrayRoot, value.length, response);1899  }1900  return value;1901}19021903const DEFAULT_MAX_ARRAY_NESTING = 1000000;19041905// Limit BigInt size to prevent CPU exhaustion from parsing very large values.1906// 300 digits covers most practical use cases (even 512-bit integers need only1907// ~154 digits) and aligns with the implicit limit from the Number approximation1908// checks in fulfillReference and getOutlinedModel.1909const MAX_BIGINT_DIGITS = 300;19101911export const MAX_BOUND_ARGS = 1000;19121913export function createResponse(1914  bundlerConfig: ServerManifest,1915  formFieldPrefix: string,1916  temporaryReferences: void | TemporaryReferenceSet,1917  backingFormData?: FormData = new FormData(),1918  arraySizeLimit?: number = DEFAULT_MAX_ARRAY_NESTING,1919): Response {1920  const chunks: Map<number, SomeChunk<any>> = new Map();19211922  const response: Response = {1923    _bundlerConfig: bundlerConfig,1924    _prefix: formFieldPrefix,1925    _formData: createBackingFormData(backingFormData),1926    _chunks: chunks,1927    _closed: false,1928    _closedReason: null,1929    _temporaryReferences: temporaryReferences,1930    _rootArrayContexts: new WeakMap(),1931    _arraySizeLimit: arraySizeLimit,1932  };1933  return response;1934}19351936export function resolveField(1937  response: Response,1938  key: string,1939  value: string,1940): void {1941  // Add this field to the backing store.1942  appendBackingEntry(response._formData, key, value);1943  const prefix = response._prefix;1944  if (key.startsWith(prefix)) {1945    const chunks = response._chunks;1946    const id = +key.slice(prefix.length);1947    const chunk = chunks.get(id);1948    if (chunk) {1949      // We were waiting on this key so now we can resolve it.1950      resolveModelChunk(response, chunk, value, id);1951    }1952  }1953}19541955export function resolveFile(response: Response, key: string, file: File): void {1956  // Add this field to the backing store.1957  appendBackingEntry(response._formData, key, file);1958}19591960export opaque type FileHandle = {1961  chunks: Array<Uint8Array>,1962  filename: string,1963  mime: string,1964};19651966export function resolveFileInfo(1967  response: Response,1968  key: string,1969  filename: string,1970  mime: string,1971): FileHandle {1972  return {1973    chunks: [],1974    filename,1975    mime,1976  };1977}19781979export function resolveFileChunk(1980  response: Response,1981  handle: FileHandle,1982  chunk: Uint8Array,1983): void {1984  handle.chunks.push(chunk);1985}19861987export function resolveFileComplete(1988  response: Response,1989  key: string,1990  handle: FileHandle,1991): void {1992  // Add this file to the backing store.1993  // Node.js doesn't expose a global File constructor so we need to use1994  // the append() form that takes the file name as the third argument,1995  // to create a File object.1996  const blob = new Blob(handle.chunks, {type: handle.mime});1997  appendBackingFile(response._formData, key, blob, handle.filename);1998}19992000export function close(response: Response): void {

Code quality findings 100

Chain Promises properly to avoid callback hell
info correctness unresolved-promise
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
Chain Promises properly to avoid callback hell
info correctness unresolved-promise
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
Chain Promises properly to avoid callback hell
info correctness unresolved-promise
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
Chain Promises properly to avoid callback hell
info correctness unresolved-promise
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
Chain Promises properly to avoid callback hell
info correctness unresolved-promise
then(resolve: (ReadableStream) => mixed, reject?: (mixed) => mixed): void,
Chain Promises properly to avoid callback hell
info correctness unresolved-promise
then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
Ensure all cases are handled or a default case is present
info correctness switch-without-default
switch (chunk.status) {
Ensure all cases are handled or a default case is present
info correctness switch-without-default
switch (chunk.status) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (typeof resolve === 'function') {
Be cautious with typeof; it has limitations (e.g., typeof null === 'object')
info correctness typeof-pitfall
if (typeof resolve === 'function') {
Be cautious with typeof; it has limitations (e.g., typeof null === 'object')
info correctness typeof-pitfall
const visited = new Set<typeof ReactPromise>();
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
inspectedValue === chunk ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (typeof reject === 'function') {
Be cautious with typeof; it has limitations (e.g., typeof null === 'object')
info correctness typeof-pitfall
if (typeof reject === 'function') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (inspectedValue.status === INITIALIZED) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (typeof resolve === 'function') {
Be cautious with typeof; it has limitations (e.g., typeof null === 'object')
info correctness typeof-pitfall
if (typeof resolve === 'function') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (chunk.value === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (typeof reject === 'function') {
Be cautious with typeof; it has limitations (e.g., typeof null === 'object')
info correctness typeof-pitfall
if (typeof reject === 'function') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (chunk.reason === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (typeof reject === 'function') {
Be cautious with typeof; it has limitations (e.g., typeof null === 'object')
info correctness typeof-pitfall
if (typeof reject === 'function') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (typeof listener === 'function') {
Be cautious with typeof; it has limitations (e.g., typeof null === 'object')
info correctness typeof-pitfall
if (typeof listener === 'function') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (typeof listener === 'function') {
Be cautious with typeof; it has limitations (e.g., typeof null === 'object')
info correctness typeof-pitfall
if (typeof listener === 'function') {
Ensure all cases are handled or a default case is present
info correctness switch-without-default
switch (chunk.status) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (chunk.status !== PENDING && chunk.status !== BLOCKED) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (listeners !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (chunk.status !== PENDING) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (value[0] === 'C') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
controller.close(value === 'C' ? '"$undefined"' : value.slice(1));
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (resolveListeners !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (typeof id !== 'string') {
Be cautious with typeof; it has limitations (e.g., typeof null === 'object')
info correctness typeof-pitfall
if (typeof id !== 'string') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (key === 'then') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (cachedPromise !== undefined) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (cachedPromise.status === INITIALIZED) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (key === __PROTO__) {
Chain Promises properly to avoid callback hell
info correctness unresolved-promise
cachedPromise.then(
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (resolveListeners !== null) {
Chain Promises properly to avoid callback hell
info correctness unresolved-promise
// Notify any resolve listeners that were added via .then() from
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (rejectListeners !== null) {
Chain Promises properly to avoid callback hell
info correctness unresolved-promise
// Notify any reject listeners that were added via .then() from subsequent
Chain Promises properly to avoid callback hell
info correctness unresolved-promise
serverReferencePromise.then(fulfill, reject);
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (typeof value === 'string') {
Be cautious with typeof; it has limitations (e.g., typeof null === 'object')
info correctness typeof-pitfall
if (typeof value === 'string') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (typeof value === 'object' && value !== null) {
Be cautious with typeof; it has limitations (e.g., typeof null === 'object')
info correctness typeof-pitfall
if (typeof value === 'object' && value !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
reference !== undefined &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
response._temporaryReferences !== undefined
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (arrayRoot === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
reference !== undefined ? reference + ':' + i : undefined;
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (key === __PROTO__) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
reference !== undefined && key.indexOf(':') === -1
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (newValue !== undefined) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
const rootReference = id === -1 ? undefined : id.toString(16);
Ensure try blocks have corresponding catch or finally blocks
info correctness try-without-catch
try {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (resolveListeners !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (typeof listener === 'function') {
Be cautious with typeof; it has limitations (e.g., typeof null === 'object')
info correctness typeof-pitfall
if (typeof listener === 'function') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (initializingHandler !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (chunk.status === PENDING) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
} else if (chunk.status === INITIALIZED) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (initializedChunk.reason !== null) {
Be cautious with typeof; it has limitations (e.g., typeof null === 'object')
info correctness typeof-pitfall
// $FlowFixMe[method-unbinding] Just doing a typeof check
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (typeof maybeController.error === 'function') {
Be cautious with typeof; it has limitations (e.g., typeof null === 'object')
info correctness typeof-pitfall
if (typeof maybeController.error === 'function') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (typeof backingEntry === 'string') {
Be cautious with typeof; it has limitations (e.g., typeof null === 'object')
info correctness typeof-pitfall
if (typeof backingEntry === 'string') {
Ensure try blocks have corresponding catch or finally blocks
info correctness try-without-catch
try {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
typeof value === 'object' &&
Be cautious with typeof; it has limitations (e.g., typeof null === 'object')
info correctness typeof-pitfall
typeof value === 'object' &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
value !== null &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
(getPrototypeOf(value) === ObjectPrototype ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
getPrototypeOf(value) === ArrayPrototype) &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (typeof value === 'string') {
Be cautious with typeof; it has limitations (e.g., typeof null === 'object')
info correctness typeof-pitfall
if (typeof value === 'string') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
} else if (typeof value === 'bigint') {
Be cautious with typeof; it has limitations (e.g., typeof null === 'object')
info correctness typeof-pitfall
} else if (typeof value === 'bigint') {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (n === 0) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (referenceArrayRoot !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (arrayRoot !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (key !== __PROTO__) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (key === '' && handler.value === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (handler.deps === 0) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (chunk === null || chunk.status !== BLOCKED) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (resolveListeners !== null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (chunk === null || chunk.status !== BLOCKED) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (referencedChunk.value === null) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (referencedChunk.reason === null) {
Ensure all cases are handled or a default case is present
info correctness switch-without-default
switch (chunk.status) {
Ensure all cases are handled or a default case is present
info correctness switch-without-default
switch (chunk.status) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (arrayRootOrController !== null && 'error' in arrayRootOrController) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
typeof value === 'object' &&
Be cautious with typeof; it has limitations (e.g., typeof null === 'object')
info correctness typeof-pitfall
typeof value === 'object' &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
value !== null &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
(getPrototypeOf(value) === ObjectPrototype ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
getPrototypeOf(value) === ArrayPrototype) &&

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.