packages/react-server/src/ReactFlightReplyServer.js JAVASCRIPT 2,017 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 2,017.
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 .then129function reactPromiseThen<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}201// The shadowing `then` must be defined with `Object.defineProperty` instead of202// assignment. Assignment would throw when `Promise.prototype` is frozen (e.g.203// by SES lockdown) because assigning over an inherited non-writable property204// is rejected.205Object.defineProperty(ReactPromise.prototype, 'then', {206  writable: true,207  enumerable: true,208  configurable: true,209  value: reactPromiseThen,210});211212const ObjectPrototype = Object.prototype;213const ArrayPrototype = Array.prototype;214215export type Response = {216  _bundlerConfig: ServerManifest,217  _prefix: string,218  _formData: BackingFormData,219  _chunks: Map<number, SomeChunk<any>>,220  _closed: boolean,221  _closedReason: mixed,222  _temporaryReferences: void | TemporaryReferenceSet,223  _rootArrayContexts: WeakMap<$ReadOnlyArray<mixed>, NestedArrayContext>,224  _arraySizeLimit: number,225};226227export function getRoot<T>(response: Response): Thenable<T> {228  const chunk = getChunk(response, 0);229  return chunk as any;230}231232function createPendingChunk<T>(response: Response): PendingChunk<T> {233  // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors234  return new ReactPromise(PENDING, null, null);235}236237function wakeChunk<T>(238  response: Response,239  listeners: Array<InitializationReference | (T => mixed)>,240  value: T,241  chunk: InitializedChunk<T>,242): void {243  for (let i = 0; i < listeners.length; i++) {244    const listener = listeners[i];245    if (typeof listener === 'function') {246      listener(value);247    } else {248      fulfillReference(response, listener, value, chunk.reason);249    }250  }251}252253function rejectChunk(254  response: Response,255  listeners: Array<InitializationReference | (mixed => mixed)>,256  error: mixed,257): void {258  for (let i = 0; i < listeners.length; i++) {259    const listener = listeners[i];260    if (typeof listener === 'function') {261      listener(error);262    } else {263      rejectReference(response, listener.handler, error);264    }265  }266}267268function wakeChunkIfInitialized<T>(269  response: Response,270  chunk: SomeChunk<T>,271  resolveListeners: Array<InitializationReference | (T => mixed)>,272  rejectListeners: null | Array<InitializationReference | (mixed => mixed)>,273): void {274  switch (chunk.status) {275    case INITIALIZED:276      wakeChunk(response, resolveListeners, chunk.value, chunk);277      break;278    case BLOCKED:279    case PENDING:280      if (chunk.value) {281        for (let i = 0; i < resolveListeners.length; i++) {282          chunk.value.push(resolveListeners[i]);283        }284      } else {285        chunk.value = resolveListeners;286      }287288      if (chunk.reason) {289        if (rejectListeners) {290          for (let i = 0; i < rejectListeners.length; i++) {291            chunk.reason.push(rejectListeners[i]);292          }293        }294      } else {295        chunk.reason = rejectListeners;296      }297      break;298    case ERRORED:299      if (rejectListeners) {300        rejectChunk(response, rejectListeners, chunk.reason);301      }302      break;303  }304}305306function triggerErrorOnChunk<T>(307  response: Response,308  chunk: SomeChunk<T>,309  error: mixed,310): void {311  if (chunk.status !== PENDING && chunk.status !== BLOCKED) {312    // If we get more data to an already resolved ID, we assume that it's313    // a stream chunk since any other row shouldn't have more than one entry.314    const streamChunk: InitializedStreamChunk<any> = chunk as any;315    const controller = streamChunk.reason;316    // $FlowFixMe[incompatible-type]: The error method should accept mixed.317    controller.error(error);318    return;319  }320  const listeners = chunk.reason;321  const erroredChunk: ErroredChunk<T> = chunk as any;322  erroredChunk.status = ERRORED;323  erroredChunk.reason = error;324  if (listeners !== null) {325    rejectChunk(response, listeners, error);326  }327}328329function createResolvedModelChunk<T>(330  response: Response,331  value: string,332  id: number,333): ResolvedModelChunk<T> {334  // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors335  return new ReactPromise(RESOLVED_MODEL, value, {336    id,337    [RESPONSE_SYMBOL]: response,338  });339}340341function createErroredChunk<T>(342  response: Response,343  reason: mixed,344): ErroredChunk<T> {345  // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors346  return new ReactPromise(ERRORED, null, reason);347}348349function resolveModelChunk<T>(350  response: Response,351  chunk: SomeChunk<T>,352  value: string,353  id: number,354): void {355  if (chunk.status !== PENDING) {356    // If we get more data to an already resolved ID, we assume that it's357    // a stream chunk since any other row shouldn't have more than one entry.358    const streamChunk: InitializedStreamChunk<any> = chunk as any;359    const controller = streamChunk.reason;360    if (value[0] === 'C') {361      controller.close(value === 'C' ? '"$undefined"' : value.slice(1));362    } else {363      controller.enqueueModel(value);364    }365    return;366  }367  const resolveListeners = chunk.value;368  const rejectListeners = chunk.reason;369  const resolvedChunk: ResolvedModelChunk<T> = chunk as any;370  resolvedChunk.status = RESOLVED_MODEL;371  resolvedChunk.value = value;372  resolvedChunk.reason = {id, [RESPONSE_SYMBOL]: response};373  if (resolveListeners !== null) {374    // This is unfortunate that we're reading this eagerly if375    // we already have listeners attached since they might no376    // longer be rendered or might not be the highest pri.377    initializeModelChunk(resolvedChunk);378    // The status might have changed after initialization.379    wakeChunkIfInitialized(response, chunk, resolveListeners, rejectListeners);380  }381}382383function createInitializedStreamChunk<384  T: ReadableStream | $AsyncIterable<any, any, void>,385>(386  response: Response,387  value: T,388  controller: FlightStreamController,389): InitializedChunk<T> {390  // We use the reason field to stash the controller since we already have that391  // field. It's a bit of a hack but efficient.392  // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors393  return new ReactPromise(INITIALIZED, value, controller);394}395396function createResolvedIteratorResultChunk<T>(397  response: Response,398  value: string,399  done: boolean,400): ResolvedModelChunk<IteratorResult<T, T>> {401  // To reuse code as much code as possible we add the wrapper element as part of the JSON.402  const iteratorResultJSON =403    (done ? '{"done":true,"value":' : '{"done":false,"value":') + value + '}';404  // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors405  return new ReactPromise(RESOLVED_MODEL, iteratorResultJSON, {406    id: -1,407    [RESPONSE_SYMBOL]: response,408  });409}410411function resolveIteratorResultChunk<T>(412  response: Response,413  chunk: SomeChunk<IteratorResult<T, T>>,414  value: string,415  done: boolean,416): void {417  // To reuse code as much code as possible we add the wrapper element as part of the JSON.418  const iteratorResultJSON =419    (done ? '{"done":true,"value":' : '{"done":false,"value":') + value + '}';420  resolveModelChunk(response, chunk, iteratorResultJSON, -1);421}422423function loadServerReference<A: Iterable<any>, T>(424  response: Response,425  metaData: {426    id: any,427    bound: null | Thenable<Array<any>>,428  },429  parentObject: Object,430  key: string,431): (...A) => Promise<T> {432  const id: ServerReferenceId = metaData.id;433  if (typeof id !== 'string') {434    return null as any;435  }436  if (key === 'then') {437    // This should never happen because we always serialize objects with then-functions438    // as "thenable" which reduces to ReactPromise with no other fields.439    return null as any;440  }441442  // Check for a cached promise from a previous call with the same metadata.443  // This handles deduplication when the same server reference appears multiple444  // times in the payload.445  const cachedPromise: SomeChunk<T> | void = (metaData as any).$$promise;446  if (cachedPromise !== undefined) {447    if (cachedPromise.status === INITIALIZED) {448      // The value was already resolved by a previous call.449      const resolvedValue: T = cachedPromise.value;450      if (key === __PROTO__) {451        return null as any;452      }453      parentObject[key] = resolvedValue;454      return resolvedValue as any;455    }456457    // The promise is still blocked. Increment the handler dependency count ...458    let handler: InitializationHandler;459    if (initializingHandler) {460      handler = initializingHandler;461      handler.deps++;462    } else {463      handler = initializingHandler = {464        chunk: null,465        value: null,466        reason: null,467        deps: 1,468        errored: false,469      };470    }471    // ... and register resolve and reject listeners on the promise.472    cachedPromise.then(473      resolveReference.bind(null, response, handler, parentObject, key),474      rejectReference.bind(null, response, handler),475    );476477    // Return a place holder value for now.478    return null as any;479  }480481  // This is the first call for this server reference metadata. Create a cached482  // promise to be used for subsequent calls.483  // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors484  const blockedPromise: BlockedChunk<T> = new ReactPromise(BLOCKED, null, null);485  (metaData as any).$$promise = blockedPromise;486487  const serverReference: ServerReference<T> =488    resolveServerReference<$FlowFixMe>(response._bundlerConfig, id);489  // We expect most servers to not really need this because you'd just have all490  // the relevant modules already loaded but it allows for lazy loading of code491  // if needed.492  const bound = metaData.bound;493  let serverReferencePromise: null | Thenable<any> =494    preloadModule(serverReference);495  if (!serverReferencePromise) {496    if (bound instanceof ReactPromise) {497      serverReferencePromise = Promise.resolve(bound);498    } else {499      const resolvedValue = requireModule(serverReference) as any;500      // Resolve the cached promise synchronously.501      const initializedPromise: InitializedChunk<T> = blockedPromise as any;502      initializedPromise.status = INITIALIZED;503      initializedPromise.value = resolvedValue;504      initializedPromise.reason = null;505      return resolvedValue;506    }507  } else if (bound instanceof ReactPromise) {508    serverReferencePromise = Promise.all([serverReferencePromise, bound]);509  }510511  let handler: InitializationHandler;512  if (initializingHandler) {513    handler = initializingHandler;514    handler.deps++;515  } else {516    handler = initializingHandler = {517      chunk: null,518      value: null,519      reason: null,520      deps: 1,521      errored: false,522    };523  }524525  function fulfill(): void {526    let resolvedValue = requireModule(serverReference) as any;527528    if (metaData.bound) {529      // This promise is coming from us and should have initialized by now.530      const promiseValue = (metaData.bound as any).value;531      const boundArgs: Array<any> = isArray(promiseValue)532        ? promiseValue.slice(0)533        : [];534      if (boundArgs.length > MAX_BOUND_ARGS) {535        reject(536          new Error(537            'Server Function has too many bound arguments. Received ' +538              boundArgs.length +539              ' but the limit is ' +540              MAX_BOUND_ARGS +541              '.',542          ),543        );544        return;545      }546      boundArgs.unshift(null); // this547      resolvedValue = resolvedValue.bind.apply(resolvedValue, boundArgs);548    }549550    // Resolve the cached promise so subsequent references can use the value.551    const resolveListeners = blockedPromise.value;552    const initializedPromise: InitializedChunk<T> = blockedPromise as any;553    initializedPromise.status = INITIALIZED;554    initializedPromise.value = resolvedValue;555    initializedPromise.reason = null;556    if (resolveListeners !== null) {557      // Notify any resolve listeners that were added via .then() from558      // subsequent loadServerReference calls for the same reference.559      wakeChunk(response, resolveListeners, resolvedValue, initializedPromise);560    }561562    resolveReference(response, handler, parentObject, key, resolvedValue);563  }564565  function reject(error: mixed): void {566    // Mark the cached promise as errored so subsequent references fail too.567    const rejectListeners = blockedPromise.reason;568    const erroredPromise: ErroredChunk<T> = blockedPromise as any;569    erroredPromise.status = ERRORED;570    erroredPromise.value = null;571    erroredPromise.reason = error;572    if (rejectListeners !== null) {573      // Notify any reject listeners that were added via .then() from subsequent574      // loadServerReference calls for the same reference.575      rejectChunk(response, rejectListeners, error);576    }577578    rejectReference(response, handler, error);579  }580581  serverReferencePromise.then(fulfill, reject);582583  // Return a place holder value for now.584  return null as any;585}586587function reviveModel(588  response: Response,589  parentObj: any,590  parentKey: string,591  value: JSONValue,592  reference: void | string,593  arrayRoot: null | NestedArrayContext,594): any {595  if (typeof value === 'string') {596    // We can't use .bind here because we need the "this" value.597    return parseModelString(598      response,599      parentObj,600      parentKey,601      value,602      reference,603      arrayRoot,604    );605  }606  if (typeof value === 'object' && value !== null) {607    if (608      reference !== undefined &&609      response._temporaryReferences !== undefined610    ) {611      // Store this object's reference in case it's returned later.612      registerTemporaryReference(613        response._temporaryReferences,614        value,615        reference,616      );617    }618    if (isArray(value)) {619      let childContext: NestedArrayContext;620      if (arrayRoot === null) {621        childContext = {622          count: 0,623          fork: false,624        } as NestedArrayContext;625        response._rootArrayContexts.set(value, childContext);626      } else {627        childContext = arrayRoot;628      }629      if (value.length > 1) {630        childContext.fork = true;631      }632      bumpArrayCount(633        childContext,634        // Number of commas + square brackets635        // value.length - 1 + 2636        value.length + 1,637        response,638      );639      for (let i = 0; i < value.length; i++) {640        const childRef =641          reference !== undefined ? reference + ':' + i : undefined;642        // $FlowFixMe[cannot-write]643        value[i] = reviveModel(644          response,645          value,646          '' + i,647          value[i],648          childRef,649          childContext,650        );651      }652    } else {653      for (const key in value) {654        if (hasOwnProperty.call(value, key)) {655          if (key === __PROTO__) {656            // $FlowFixMe[cannot-write]657            delete value[key];658            continue;659          }660          const childRef =661            reference !== undefined && key.indexOf(':') === -1662              ? reference + ':' + key663              : undefined;664          const newValue = reviveModel(665            response,666            value,667            key,668            value[key],669            childRef,670            null, // The array context resets when we're entering a non-array671          );672          if (newValue !== undefined) {673            // $FlowFixMe[cannot-write]674            value[key] = newValue;675          } else {676            // $FlowFixMe[cannot-write]677            delete value[key];678          }679        }680      }681    }682  }683  return value;684}685686type NestedArrayContext = {687  // Keeps track of how many slots, bytes or characters are in nested arrays/strings/typed arrays.688  count: number,689  // A single child is itself not harmful. There needs to be at least one parent array with more690  // than one child.691  fork: boolean,692};693694function bumpArrayCount(695  arrayContext: NestedArrayContext,696  slots: number,697  response: Response,698): void {699  const newCount = (arrayContext.count += slots);700  if (newCount > response._arraySizeLimit && arrayContext.fork) {701    throw new Error(702      'Maximum array nesting exceeded. Large nested arrays can be dangerous. Try adding intermediate objects.',703    );704  }705}706707type InitializationReference = {708  handler: InitializationHandler,709  parentObject: Object,710  key: string,711  map: (712    response: Response,713    model: any,714    parentObject: Object,715    key: string,716  ) => any,717  path: Array<string>,718  arrayRoot: null | NestedArrayContext,719};720type InitializationHandler = {721  chunk: null | BlockedChunk<any>,722  value: any,723  // TODO: Split type to make it impossible to treat a thrown value as NestedArrayContext.724  // thrown value if errored, otherwise array context725  reason: mixed | NestedArrayContext,726  deps: number,727  errored: boolean,728};729let initializingHandler: null | InitializationHandler = null;730731function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {732  const prevHandler = initializingHandler;733  initializingHandler = null;734735  const {[RESPONSE_SYMBOL]: response, id} = chunk.reason;736737  const rootReference = id === -1 ? undefined : id.toString(16);738739  const resolvedModel = chunk.value;740741  // We go to the BLOCKED state until we've fully resolved this.742  // We do this before parsing in case we try to initialize the same chunk743  // while parsing the model. Such as in a cyclic reference.744  const cyclicChunk: BlockedChunk<T> = chunk as any;745  cyclicChunk.status = BLOCKED;746  cyclicChunk.value = null;747  cyclicChunk.reason = null;748749  try {750    const rawModel = JSON.parse(resolvedModel);751752    // The root might not be an array but if it is we want to track the count of entries.753    const arrayRoot: NestedArrayContext = {754      count: 0,755      fork: false,756    };757758    const value: T = reviveModel(759      response,760      {'': rawModel},761      '',762      rawModel,763      rootReference,764      arrayRoot,765    );766767    // Invoke any listeners added while resolving this model. I.e. cyclic768    // references. This may or may not fully resolve the model depending on769    // if they were blocked.770    const resolveListeners = cyclicChunk.value;771    if (resolveListeners !== null) {772      cyclicChunk.value = null;773      cyclicChunk.reason = null;774      for (let i = 0; i < resolveListeners.length; i++) {775        const listener = resolveListeners[i];776        if (typeof listener === 'function') {777          listener(value);778        } else {779          fulfillReference(response, listener, value, arrayRoot);780        }781      }782    }783    if (initializingHandler !== null) {784      if (initializingHandler.errored) {785        throw initializingHandler.reason;786      }787      if (initializingHandler.deps > 0) {788        // We discovered new dependencies on modules that are not yet resolved.789        // We have to keep the BLOCKED state until they're resolved.790        initializingHandler.value = value;791        initializingHandler.reason = arrayRoot;792        initializingHandler.chunk = cyclicChunk;793        return;794      }795    }796    const initializedChunk: InitializedChunk<T> = chunk as any;797    initializedChunk.status = INITIALIZED;798    initializedChunk.value = value;799    initializedChunk.reason = arrayRoot;800  } catch (error) {801    const erroredChunk: ErroredChunk<T> = chunk as any;802    erroredChunk.status = ERRORED;803    erroredChunk.reason = error;804  } finally {805    initializingHandler = prevHandler;806  }807}808809// Report that any missing chunks in the model is now going to throw this810// error upon read. Also notify any pending promises.811export function reportGlobalError(response: Response, error: Error): void {812  response._closed = true;813  response._closedReason = error;814  response._chunks.forEach(chunk => {815    // If this chunk was already resolved or errored, it won't816    // trigger an error but if it wasn't then we need to817    // because we won't be getting any new data to resolve it.818    if (chunk.status === PENDING) {819      triggerErrorOnChunk(response, chunk, error);820    } else if (chunk.status === INITIALIZED) {821      const initializedChunk:822        | InitializedChunk<any>823        | InitializedStreamChunk<any> = chunk as any;824      if (initializedChunk.reason !== null) {825        const maybeController = initializedChunk.reason;826        // $FlowFixMe[method-unbinding] Just doing a typeof check827        if (typeof maybeController.error === 'function') {828          maybeController.error(error);829        }830      }831    }832  });833}834835function getChunk(response: Response, id: number): SomeChunk<any> {836  const chunks = response._chunks;837  let chunk = chunks.get(id);838  if (!chunk) {839    const prefix = response._prefix;840    const key = prefix + id;841    // Check if we have this field in the backing store already.842    const backingEntry = getBackingEntry(response._formData, key);843    if (typeof backingEntry === 'string') {844      chunk = createResolvedModelChunk(response, backingEntry, id);845    } else if (response._closed) {846      // We have already errored the response and we're not going to get847      // anything more streaming in so this will immediately error.848      chunk = createErroredChunk(response, response._closedReason);849    } else {850      // We're still waiting on this entry to stream in.851      chunk = createPendingChunk(response);852    }853    chunks.set(id, chunk);854  }855  return chunk;856}857858function fulfillReference(859  response: Response,860  reference: InitializationReference,861  value: any,862  arrayRoot: null | NestedArrayContext,863): void {864  const {handler, parentObject, key, map, path} = reference;865866  let resolvedValue;867  try {868    let localLength: number = 0;869    const rootArrayContexts = response._rootArrayContexts;870    for (let i = 1; i < path.length; i++) {871      // The server doesn't have any lazy references so we don't expect to go through a Promise.872      const name = path[i];873      if (874        typeof value === 'object' &&875        value !== null &&876        (getPrototypeOf(value) === ObjectPrototype ||877          getPrototypeOf(value) === ArrayPrototype) &&878        hasOwnProperty.call(value, name)879      ) {880        value = value[name];881        if (isArray(value)) {882          localLength = 0;883          arrayRoot = rootArrayContexts.get(value) || arrayRoot;884        } else {885          arrayRoot = null;886          if (typeof value === 'string') {887            localLength = value.length;888          } else if (typeof value === 'bigint') {889            // Estimate the length to avoid expensive toString() calls on large890            // BigInt values. If the value is too large, we get Infinity, which891            // will trigger the array size limit error.892            // eslint-disable-next-line react-internal/no-primitive-constructors893            const n = Math.abs(Number(value));894            if (n === 0) {895              localLength = 1;896            } else {897              localLength = Math.floor(Math.log10(n)) + 1;898            }899          } else if (ArrayBuffer.isView(value)) {900            localLength = value.byteLength;901          } else {902            localLength = 0;903          }904        }905      } else {906        throw new Error('Invalid reference.');907      }908    }909910    resolvedValue = map(response, value, parentObject, key);911912    // Add any array counts to the reference's array root. The value that we're913    // resolving might have deep nesting that we need to resolve.914    const referenceArrayRoot = reference.arrayRoot;915    if (referenceArrayRoot !== null) {916      if (arrayRoot !== null) {917        if (arrayRoot.fork) {918          referenceArrayRoot.fork = true;919        }920        bumpArrayCount(referenceArrayRoot, arrayRoot.count, response);921      } else if (localLength > 0) {922        bumpArrayCount(referenceArrayRoot, localLength, response);923      }924    }925  } catch (error) {926    rejectReference(response, handler, error);927    return;928  }929930  // There are no Elements or Debug Info to transfer here.931932  resolveReference(response, handler, parentObject, key, resolvedValue);933}934935function resolveReference(936  response: Response,937  handler: InitializationHandler,938  parentObject: Object,939  key: string,940  resolvedValue: mixed,941): void {942  if (key !== __PROTO__) {943    parentObject[key] = resolvedValue;944  }945946  // If this is the root object for a model reference, where `handler.value`947  // is a stale `null`, the resolved value can be used directly.948  if (key === '' && handler.value === null) {949    handler.value = resolvedValue;950  }951952  handler.deps--;953954  if (handler.deps === 0) {955    const chunk = handler.chunk;956    if (chunk === null || chunk.status !== BLOCKED) {957      return;958    }959    const resolveListeners = chunk.value;960    const initializedChunk: InitializedChunk<any> = chunk as any;961    initializedChunk.status = INITIALIZED;962    initializedChunk.value = handler.value;963    initializedChunk.reason =964      // $FlowFixMe[incompatible-type] Assuming handler.errored is false.965      handler.reason;966    if (resolveListeners !== null) {967      wakeChunk(response, resolveListeners, handler.value, initializedChunk);968    }969  }970}971972function rejectReference(973  response: Response,974  handler: InitializationHandler,975  error: mixed,976): void {977  if (handler.errored) {978    // We've already errored. We could instead build up an AggregateError979    // but if there are multiple errors we just take the first one like980    // Promise.all.981    return;982  }983  handler.errored = true;984  handler.value = null;985  handler.reason = error;986  const chunk = handler.chunk;987  if (chunk === null || chunk.status !== BLOCKED) {988    return;989  }990  // There's no debug info to forward in this direction.991  triggerErrorOnChunk(response, chunk, error);992}993994function waitForReference<T>(995  response: Response,996  referencedChunk: BlockedChunk<T>,997  parentObject: Object,998  key: string,999  arrayRoot: null | NestedArrayContext,1000  map: (response: Response, model: any, parentObject: Object, key: string) => T,1001  path: Array<string>,1002): T {1003  let handler: InitializationHandler;1004  if (initializingHandler) {1005    handler = initializingHandler;1006    handler.deps++;1007  } else {1008    handler = initializingHandler = {1009      chunk: null,1010      value: null,1011      reason: null,1012      deps: 1,1013      errored: false,1014    };1015  }10161017  const reference: InitializationReference = {1018    handler,1019    parentObject,1020    key,1021    map,1022    path,1023    arrayRoot,1024  };10251026  // Add "listener".1027  if (referencedChunk.value === null) {1028    referencedChunk.value = [reference];1029  } else {1030    referencedChunk.value.push(reference);1031  }1032  if (referencedChunk.reason === null) {1033    referencedChunk.reason = [reference];1034  } else {1035    referencedChunk.reason.push(reference);1036  }10371038  // Return a place holder value for now.1039  return null as any;1040}10411042function getOutlinedModel<T>(1043  response: Response,1044  reference: string,1045  parentObject: Object,1046  key: string,1047  referenceArrayRoot: null | NestedArrayContext,1048  map: (response: Response, model: any, parentObject: Object, key: string) => T,1049): T {1050  const path = reference.split(':');1051  const id = parseInt(path[0], 16);1052  let chunk = getChunk(response, id);1053  switch (chunk.status) {1054    case RESOLVED_MODEL:1055      initializeModelChunk(chunk);1056      // $FlowFixMe[incompatible-type] We just initialized this chunk so it can't be a ResolvedModelChunk anymore.1057      chunk = chunk as Exclude<SomeChunk<T>, ResolvedModelChunk<T>>;1058      break;1059  }1060  // The status might have changed after initialization.1061  switch (chunk.status) {1062    case INITIALIZED:1063      let value = chunk.value;1064      const arrayRootOrController:1065        | null1066        | NestedArrayContext1067        | FlightStreamController = chunk.reason;1068      if (arrayRootOrController !== null && 'error' in arrayRootOrController) {1069        throw new Error(1070          'Expected an initialized chunk but got an initialized stream chunk instead. ' +1071            'This payload may have been submitted by an older version of React.',1072        );1073      }1074      let arrayRoot = arrayRootOrController;10751076      let localLength: number = 0;1077      const rootArrayContexts = response._rootArrayContexts;1078      for (let i = 1; i < path.length; i++) {1079        const name = path[i];1080        if (1081          typeof value === 'object' &&1082          // $FlowFixMe[invalid-compare] This check is still needed at runtime.1083          value !== null &&1084          (getPrototypeOf(value) === ObjectPrototype ||1085            getPrototypeOf(value) === ArrayPrototype) &&1086          hasOwnProperty.call(value, name)1087        ) {1088          value = value[name];1089          if (isArray(value)) {1090            localLength = 0;1091            arrayRoot =1092              rootArrayContexts.get(1093                // $FlowFixMe[incompatible-type] Our `isArray` typing can't narrow `mixed`1094                value as $ReadOnlyArray<mixed>,1095              ) || arrayRoot;1096          } else {1097            arrayRoot = null;1098            if (typeof value === 'string') {1099              localLength = value.length;1100            } else if (typeof value === 'bigint') {1101              // Estimate the length to avoid expensive toString() calls on large1102              // BigInt values. If the value is too large, we get Infinity, which1103              // will trigger the array size limit error.1104              // eslint-disable-next-line react-internal/no-primitive-constructors1105              const n = Math.abs(Number(value));1106              if (n === 0) {1107                localLength = 1;1108              } else {1109                localLength = Math.floor(Math.log10(n)) + 1;1110              }1111            } else if (ArrayBuffer.isView(value)) {1112              localLength = value.byteLength;1113            } else {1114              localLength = 0;1115            }1116          }1117        } else {1118          throw new Error('Invalid reference.');1119        }1120      }1121      const chunkValue = map(response, value, parentObject, key);11221123      // Add any array counts to the reference's array root. The value that we're1124      // resolving might have deep nesting that we need to resolve.1125      if (referenceArrayRoot !== null) {1126        if (arrayRoot !== null) {1127          if (arrayRoot.fork) {1128            referenceArrayRoot.fork = true;1129          }1130          bumpArrayCount(referenceArrayRoot, arrayRoot.count, response);1131        } else if (localLength > 0) {1132          bumpArrayCount(referenceArrayRoot, localLength, response);1133        }1134      }1135      // There's no Element nor Debug Info in the ReplyServer so we don't have to check those here.1136      return chunkValue;1137    case BLOCKED:1138      return waitForReference(1139        response,1140        chunk,1141        parentObject,1142        key,1143        referenceArrayRoot,1144        map,1145        path,1146      );1147    case PENDING:1148      // If we don't have the referenced chunk yet, then this must be a forward reference,1149      // which is not allowed.1150      throw new Error('Invalid forward reference.');1151    default:1152      // This is an error. Instead of erroring directly, we're going to encode this on1153      // an initialization handler.1154      if (initializingHandler) {1155        initializingHandler.errored = true;1156        initializingHandler.value = null;1157        initializingHandler.reason = chunk.reason;1158      } else {1159        initializingHandler = {1160          chunk: null,1161          value: null,1162          reason: chunk.reason,1163          deps: 0,1164          errored: true,1165        };1166      }1167      // Placeholder1168      return null as any;1169  }1170}11711172function createMap(1173  response: Response,1174  model: Array<[any, any]>,1175): Map<any, any> {1176  if (!isArray(model)) {1177    throw new Error('Invalid Map initializer.');1178  }1179  if ((model as any).$$consumed === true) {1180    throw new Error('Already initialized Map.');1181  }1182  // This needs to come first to prevent the model from being consumed again in case of a cyclic reference.1183  (model as any).$$consumed = true;1184  const map = new Map(model);1185  return map;1186}11871188function createSet(response: Response, model: Array<any>): Set<any> {1189  if (!isArray(model)) {1190    throw new Error('Invalid Set initializer.');1191  }1192  if ((model as any).$$consumed === true) {1193    throw new Error('Already initialized Set.');1194  }1195  // This needs to come first to prevent the model from being consumed again in case of a cyclic reference.1196  (model as any).$$consumed = true;1197  const set = new Set(model);1198  return set;1199}12001201function extractIterator(response: Response, model: Array<any>): Iterator<any> {1202  if (!isArray(model)) {1203    throw new Error('Invalid Iterator initializer.');1204  }1205  if ((model as any).$$consumed === true) {1206    throw new Error('Already initialized Iterator.');1207  }1208  // This needs to come first to prevent the model from being consumed again in case of a cyclic reference.1209  (model as any).$$consumed = true;1210  // $FlowFixMe[incompatible-use]: This uses raw Symbols because we're extracting from a native array.1211  const iterator = model[Symbol.iterator]();1212  return iterator;1213}12141215function createModel(1216  response: Response,1217  model: any,1218  parentObject: Object,1219  key: string,1220): any {1221  if (key === 'then' && typeof model === 'function') {1222    // This should never happen because we always serialize objects with then-functions1223    // as "thenable" which reduces to ReactPromise with no other fields.1224    return null;1225  }1226  return model;1227}12281229function parseTypedArray<T: $ArrayBufferView | ArrayBuffer>(1230  response: Response,1231  reference: string,1232  constructor: any,1233  bytesPerElement: number,1234  parentObject: Object,1235  parentKey: string,1236  referenceArrayRoot: null | NestedArrayContext,1237): null {1238  const id = parseInt(reference.slice(2), 16);1239  const prefix = response._prefix;1240  const key = prefix + id;1241  const chunks = response._chunks;1242  if (chunks.has(id)) {1243    throw new Error('Already initialized typed array.');1244  }1245  chunks.set(1246    id,1247    // We don't need to put the actual Blob in the chunk,1248    // because it shouldn't be accessed by anything else.1249    createErroredChunk(response, new Error('Already initialized typed array.')),1250  );12511252  // We should have this backingEntry in the store already because we emitted1253  // it before referencing it. It should be a Blob.1254  const backingEntry: Blob = getBackingEntry(response._formData, key) as any;12551256  const promise: Promise<ArrayBuffer> = backingEntry.arrayBuffer();12571258  // Since loading the buffer is an async operation we'll be blocking the parent1259  // chunk.12601261  let handler: InitializationHandler;1262  if (initializingHandler) {1263    handler = initializingHandler;1264    handler.deps++;1265  } else {1266    handler = initializingHandler = {1267      chunk: null,1268      value: null,1269      reason: null,1270      deps: 1,1271      errored: false,1272    };1273  }12741275  function fulfill(buffer: ArrayBuffer): void {1276    try {1277      if (referenceArrayRoot !== null) {1278        bumpArrayCount(referenceArrayRoot, buffer.byteLength, response);1279      }12801281      const resolvedValue: T =1282        constructor === ArrayBuffer1283          ? (buffer as any)1284          : (new constructor(buffer) as any);12851286      if (key !== __PROTO__) {1287        parentObject[parentKey] = resolvedValue;1288      }12891290      // If this is the root object for a model reference, where `handler.value`1291      // is a stale `null`, the resolved value can be used directly.1292      if (parentKey === '' && handler.value === null) {1293        handler.value = resolvedValue;1294      }1295    } catch (x) {1296      reject(x);1297      return;1298    }12991300    handler.deps--;13011302    if (handler.deps === 0) {1303      const chunk = handler.chunk;1304      if (chunk === null || chunk.status !== BLOCKED) {1305        return;1306      }1307      const resolveListeners = chunk.value;1308      const initializedChunk: InitializedChunk<T> = chunk as any;1309      initializedChunk.status = INITIALIZED;1310      initializedChunk.value = handler.value;1311      // We don't keep an array count for this since it won't be referenced again.1312      // In fact, we don't really need to store this chunk at all.1313      initializedChunk.reason = null;1314      if (resolveListeners !== null) {1315        wakeChunk(response, resolveListeners, handler.value, initializedChunk);1316      }1317    }1318  }13191320  function reject(error: mixed): void {1321    if (handler.errored) {1322      // We've already errored. We could instead build up an AggregateError1323      // but if there are multiple errors we just take the first one like1324      // Promise.all.1325      return;1326    }1327    handler.errored = true;1328    handler.value = null;1329    handler.reason = error;1330    const chunk = handler.chunk;1331    if (chunk === null || chunk.status !== BLOCKED) {1332      return;1333    }1334    triggerErrorOnChunk(response, chunk, error);1335  }13361337  promise.then(fulfill, reject);13381339  return null;1340}13411342function resolveStream<T: ReadableStream | $AsyncIterable<any, any, void>>(1343  response: Response,1344  id: number,1345  stream: T,1346  controller: FlightStreamController,1347): void {1348  const chunks = response._chunks;1349  const chunk = createInitializedStreamChunk(response, stream, controller);1350  chunks.set(id, chunk);13511352  const prefix = response._prefix;1353  const key = prefix + id;1354  const existingEntries = getAllBackingEntries(response._formData, key);1355  for (let i = 0; i < existingEntries.length; i++) {1356    const value = existingEntries[i];1357    if (typeof value === 'string') {1358      if (value[0] === 'C') {1359        controller.close(value === 'C' ? '"$undefined"' : value.slice(1));1360      } else {1361        controller.enqueueModel(value);1362      }1363    }1364  }1365}13661367function parseReadableStream<T>(1368  response: Response,1369  reference: string,1370  type: void | 'bytes',1371  parentObject: Object,1372  parentKey: string,1373): ReadableStream {1374  const id = parseInt(reference.slice(2), 16);1375  const chunks = response._chunks;1376  if (chunks.has(id)) {1377    throw new Error('Already initialized stream.');1378  }13791380  let controller: ReadableStreamController = null as any;1381  let closed = false;1382  const stream = new ReadableStream({1383    type: type,1384    start(c) {1385      controller = c;1386    },1387  });1388  let previousBlockedChunk: SomeChunk<T> | null = null;1389  function enqueue(value: T): void {1390    if (type === 'bytes' && !ArrayBuffer.isView(value)) {1391      flightController.error(new Error('Invalid data for bytes stream.'));1392      return;1393    }1394    controller.enqueue(value);1395  }1396  const flightController = {1397    enqueueModel(json: string): void {1398      if (previousBlockedChunk === null) {1399        // If we're not blocked on any other chunks, we can try to eagerly initialize1400        // this as a fast-path to avoid awaiting them.1401        const chunk: ResolvedModelChunk<T> = createResolvedModelChunk(1402          response,1403          json,1404          -1,1405        );1406        initializeModelChunk(chunk);1407        const initializedChunk: SomeChunk<T> = chunk;1408        if (initializedChunk.status === INITIALIZED) {1409          enqueue(initializedChunk.value);1410        } else {1411          chunk.then(enqueue, flightController.error);1412          previousBlockedChunk = chunk;1413        }1414      } else {1415        // We're still waiting on a previous chunk so we can't enqueue quite yet.1416        const blockedChunk = previousBlockedChunk;1417        const chunk: SomeChunk<T> = createPendingChunk(response);1418        chunk.then(enqueue, flightController.error);1419        previousBlockedChunk = chunk;1420        blockedChunk.then(function () {1421          if (previousBlockedChunk === chunk) {1422            // We were still the last chunk so we can now clear the queue and return1423            // to synchronous emitting.1424            previousBlockedChunk = null;1425          }1426          resolveModelChunk(response, chunk, json, -1);1427        });1428      }1429    },1430    close(json: string): void {1431      if (closed) {1432        return;1433      }1434      closed = true;1435      if (previousBlockedChunk === null) {1436        controller.close();1437      } else {1438        const blockedChunk = previousBlockedChunk;1439        // We shouldn't get any more enqueues after this so we can set it back to null.1440        previousBlockedChunk = null;1441        blockedChunk.then(() => controller.close());1442      }1443    },1444    error(error: mixed): void {1445      if (closed) {1446        return;1447      }1448      closed = true;1449      if (previousBlockedChunk === null) {1450        // $FlowFixMe[incompatible-type]1451        controller.error(error);1452      } else {1453        const blockedChunk = previousBlockedChunk;1454        // We shouldn't get any more enqueues after this so we can set it back to null.1455        previousBlockedChunk = null;1456        blockedChunk.then(() => controller.error(error as any));1457      }1458    },1459  };1460  resolveStream(response, id, stream, flightController);1461  return stream;1462}14631464function FlightIterator(1465  this: {next: (arg: void) => SomeChunk<IteratorResult<any, any>>, ...},1466  next: (arg: void) => SomeChunk<IteratorResult<any, any>>,1467) {1468  this.next = next;1469  // TODO: Add return/throw as options for aborting.1470}1471// TODO: The iterator could inherit the AsyncIterator prototype which is not exposed as1472// a global but exists as a prototype of an AsyncGenerator. However, it's not needed1473// to satisfy the iterable protocol.1474FlightIterator.prototype = {} as any;1475FlightIterator.prototype[ASYNC_ITERATOR] = function asyncIterator(1476  this: $AsyncIterator<any, any, void>,1477) {1478  // Self referencing iterator.1479  return this;1480};14811482function parseAsyncIterable<T>(1483  response: Response,1484  reference: string,1485  iterator: boolean,1486  parentObject: Object,1487  parentKey: string,1488): $AsyncIterable<T, T, void> | $AsyncIterator<T, T, void> {1489  const id = parseInt(reference.slice(2), 16);1490  const chunks = response._chunks;1491  if (chunks.has(id)) {1492    throw new Error('Already initialized stream.');1493  }14941495  const buffer: Array<SomeChunk<IteratorResult<T, T>>> = [];1496  let closed = false;1497  let nextWriteIndex = 0;1498  const flightController = {1499    enqueueModel(value: string): void {1500      if (nextWriteIndex === buffer.length) {1501        buffer[nextWriteIndex] = createResolvedIteratorResultChunk(1502          response,1503          value,1504          false,1505        );1506      } else {1507        resolveIteratorResultChunk(1508          response,1509          buffer[nextWriteIndex],1510          value,1511          false,1512        );1513      }1514      nextWriteIndex++;1515    },1516    close(value: string): void {1517      if (closed) {1518        return;1519      }1520      closed = true;1521      if (nextWriteIndex === buffer.length) {1522        buffer[nextWriteIndex] = createResolvedIteratorResultChunk(1523          response,1524          value,1525          true,1526        );1527      } else {1528        resolveIteratorResultChunk(1529          response,1530          buffer[nextWriteIndex],1531          value,1532          true,1533        );1534      }1535      nextWriteIndex++;1536      while (nextWriteIndex < buffer.length) {1537        // In generators, any extra reads from the iterator have the value undefined.1538        resolveIteratorResultChunk(1539          response,1540          buffer[nextWriteIndex++],1541          '"$undefined"',1542          true,1543        );1544      }1545    },1546    error(error: Error): void {1547      if (closed) {1548        return;1549      }1550      closed = true;1551      if (nextWriteIndex === buffer.length) {1552        buffer[nextWriteIndex] =1553          createPendingChunk<IteratorResult<T, T>>(response);1554      }1555      while (nextWriteIndex < buffer.length) {1556        triggerErrorOnChunk(response, buffer[nextWriteIndex++], error);1557      }1558    },1559  };1560  const iterable: $AsyncIterable<T, T, void> = {1561    [ASYNC_ITERATOR](): $AsyncIterator<T, T, void> {1562      let nextReadIndex = 0;1563      // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors1564      return new FlightIterator((arg: void) => {1565        if (arg !== undefined) {1566          throw new Error(1567            'Values cannot be passed to next() of AsyncIterables passed to Client Components.',1568          );1569        }1570        if (nextReadIndex === buffer.length) {1571          if (closed) {1572            // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors1573            return new ReactPromise(1574              INITIALIZED,1575              {done: true, value: undefined},1576              null,1577            );1578          }1579          buffer[nextReadIndex] =1580            createPendingChunk<IteratorResult<T, T>>(response);1581        }1582        return buffer[nextReadIndex++];1583      });1584    },1585  };1586  // TODO: If it's a single shot iterator we can optimize memory by cleaning up the buffer after1587  // reading through the end, but currently we favor code size over this optimization.1588  const stream = iterator ? iterable[ASYNC_ITERATOR]() : iterable;1589  resolveStream(response, id, stream, flightController);1590  return stream;1591}15921593function parseModelString(1594  response: Response,1595  obj: Object,1596  key: string,1597  value: string,1598  reference: void | string,1599  arrayRoot: null | NestedArrayContext,1600): any {1601  if (value[0] === '$') {1602    switch (value[1]) {1603      case '$': {1604        // This was an escaped string value.1605        if (arrayRoot !== null) {1606          bumpArrayCount(arrayRoot, value.length - 1, response);1607        }1608        return value.slice(1);1609      }1610      case '@': {1611        // Promise1612        const id = parseInt(value.slice(2), 16);1613        const chunk = getChunk(response, id);1614        return chunk;1615      }1616      case 'h': {1617        // Server Reference1618        const ref = value.slice(2);1619        return getOutlinedModel(1620          response,1621          ref,1622          obj,1623          key,1624          null,1625          loadServerReference,1626        );1627      }1628      case 'T': {1629        // Temporary Reference1630        if (1631          reference === undefined ||1632          response._temporaryReferences === undefined1633        ) {1634          throw new Error(1635            'Could not reference an opaque temporary reference. ' +1636              'This is likely due to misconfiguring the temporaryReferences options ' +1637              'on the server.',1638          );1639        }1640        return createTemporaryReference(1641          response._temporaryReferences,1642          reference,1643        );1644      }1645      case 'Q': {1646        // Map1647        const ref = value.slice(2);1648        return getOutlinedModel(response, ref, obj, key, null, createMap);1649      }1650      case 'W': {1651        // Set1652        const ref = value.slice(2);1653        return getOutlinedModel(response, ref, obj, key, null, createSet);1654      }1655      case 'K': {1656        // FormData1657        const stringId = value.slice(2);16581659        const responsePrefix = response._prefix;1660        // Use the special marker from the Client to distinguish keys that should1661        // be consumed by referenced FormData.1662        const anyFormPrefix = responsePrefix + '_';1663        const formPrefix = anyFormPrefix + stringId + '_';16641665        const data = new FormData();1666        const backingFormData = response._formData;1667        // We're still transpiling for-of loops, so we have to use the iterator directly instead of a for-of loop.1668        while (true) {1669          const formDataKey = peekBackingEntry(backingFormData);1670          if (formDataKey === undefined) {1671            break;1672          }1673          if (formDataKey.startsWith(formPrefix)) {1674            const referencedFormDataValue = getAllBackingEntries(1675              backingFormData,1676              formDataKey,1677            );1678            const referencedFormDataKey = formDataKey.slice(formPrefix.length);1679            for (let i = 0; i < referencedFormDataValue.length; i++) {1680              // $FlowFixMe[incompatible-type]1681              data.append(referencedFormDataKey, referencedFormDataValue[i]);1682            }1683            consumeBackingEntry(backingFormData, formDataKey);1684          } else if (formDataKey.startsWith(anyFormPrefix)) {1685            // The FormData values are continuous and before the FormData reference.1686            // If we see something that doesn't look like a value for a referenced1687            // FormData, we can assume we're past the values for this FormData1688            // reference and stop iterating.1689            break;1690          } else {1691            // Either an outlined value or something not owned by this Reply.1692            advanceBackingEntryIterator(backingFormData);1693          }1694        }1695        return data;1696      }1697      case 'i': {1698        // Iterator1699        const ref = value.slice(2);1700        return getOutlinedModel(response, ref, obj, key, null, extractIterator);1701      }1702      case 'I': {1703        // $Infinity1704        return Infinity;1705      }1706      case '-': {1707        // $-0 or $-Infinity1708        if (value === '$-0') {1709          return -0;1710        } else {1711          return -Infinity;1712        }1713      }1714      case 'N': {1715        // $NaN1716        return NaN;1717      }1718      case 'u': {1719        // matches "$undefined"1720        // Special encoding for `undefined` which can't be serialized as JSON otherwise.1721        return undefined;1722      }1723      case 'D': {1724        // Date1725        return new Date(Date.parse(value.slice(2)));1726      }1727      case 'n': {1728        // BigInt1729        const bigIntStr = value.slice(2);1730        if (bigIntStr.length > MAX_BIGINT_DIGITS) {1731          throw new Error(1732            'BigInt is too large. Received ' +1733              bigIntStr.length +1734              ' digits but the limit is ' +1735              MAX_BIGINT_DIGITS +1736              '.',1737          );1738        }1739        if (arrayRoot !== null) {1740          bumpArrayCount(arrayRoot, bigIntStr.length, response);1741        }1742        return BigInt(bigIntStr);1743      }1744      case 'A':1745        return parseTypedArray(1746          response,1747          value,1748          ArrayBuffer,1749          1,1750          obj,1751          key,1752          arrayRoot,1753        );1754      case 'O':1755        return parseTypedArray(1756          response,1757          value,1758          Int8Array,1759          1,1760          obj,1761          key,1762          arrayRoot,1763        );1764      case 'o':1765        return parseTypedArray(1766          response,1767          value,1768          Uint8Array,1769          1,1770          obj,1771          key,1772          arrayRoot,1773        );1774      case 'U':1775        return parseTypedArray(1776          response,1777          value,1778          Uint8ClampedArray,1779          1,1780          obj,1781          key,1782          arrayRoot,1783        );1784      case 'S':1785        return parseTypedArray(1786          response,1787          value,1788          Int16Array,1789          2,1790          obj,1791          key,1792          arrayRoot,1793        );1794      case 's':1795        return parseTypedArray(1796          response,1797          value,1798          Uint16Array,1799          2,1800          obj,1801          key,1802          arrayRoot,1803        );1804      case 'L':1805        return parseTypedArray(1806          response,1807          value,1808          Int32Array,1809          4,1810          obj,1811          key,1812          arrayRoot,1813        );1814      case 'l':1815        return parseTypedArray(1816          response,1817          value,1818          Uint32Array,1819          4,1820          obj,1821          key,1822          arrayRoot,1823        );1824      case 'G':1825        return parseTypedArray(1826          response,1827          value,1828          Float32Array,1829          4,1830          obj,1831          key,1832          arrayRoot,1833        );1834      case 'g':1835        return parseTypedArray(1836          response,1837          value,1838          Float64Array,1839          8,1840          obj,1841          key,1842          arrayRoot,1843        );1844      case 'M':1845        return parseTypedArray(1846          response,1847          value,1848          BigInt64Array,1849          8,1850          obj,1851          key,1852          arrayRoot,1853        );1854      case 'm':1855        return parseTypedArray(1856          response,1857          value,1858          BigUint64Array,1859          8,1860          obj,1861          key,1862          arrayRoot,1863        );1864      case 'V':1865        return parseTypedArray(1866          response,1867          value,1868          DataView,1869          1,1870          obj,1871          key,1872          arrayRoot,1873        );1874      case 'B': {1875        // Blob1876        const id = parseInt(value.slice(2), 16);1877        const prefix = response._prefix;1878        const blobKey = prefix + id;1879        // We should have this backingEntry in the store already because we emitted1880        // it before referencing it. It should be a Blob.1881        const backingEntry: Blob = getBackingEntry(1882          response._formData,1883          blobKey,1884        ) as any;1885        if (!(backingEntry instanceof Blob)) {1886          throw new Error('Referenced Blob is not a Blob.');1887        }1888        return backingEntry;1889      }1890      case 'R': {1891        return parseReadableStream(response, value, undefined, obj, key);1892      }1893      case 'r': {1894        return parseReadableStream(response, value, 'bytes', obj, key);1895      }1896      case 'X': {1897        return parseAsyncIterable(response, value, false, obj, key);1898      }1899      case 'x': {1900        return parseAsyncIterable(response, value, true, obj, key);1901      }1902    }1903    // We assume that anything else is a reference ID.1904    const ref = value.slice(1);1905    return getOutlinedModel(response, ref, obj, key, arrayRoot, createModel);1906  }1907  if (arrayRoot !== null) {1908    bumpArrayCount(arrayRoot, value.length, response);1909  }1910  return value;1911}19121913const DEFAULT_MAX_ARRAY_NESTING = 1000000;19141915// Limit BigInt size to prevent CPU exhaustion from parsing very large values.1916// 300 digits covers most practical use cases (even 512-bit integers need only1917// ~154 digits) and aligns with the implicit limit from the Number approximation1918// checks in fulfillReference and getOutlinedModel.1919const MAX_BIGINT_DIGITS = 300;19201921export const MAX_BOUND_ARGS = 1000;19221923export function createResponse(1924  bundlerConfig: ServerManifest,1925  formFieldPrefix: string,1926  temporaryReferences: void | TemporaryReferenceSet,1927  backingFormData?: FormData = new FormData(),1928  arraySizeLimit?: number = DEFAULT_MAX_ARRAY_NESTING,1929): Response {1930  const chunks: Map<number, SomeChunk<any>> = new Map();19311932  const response: Response = {1933    _bundlerConfig: bundlerConfig,1934    _prefix: formFieldPrefix,1935    _formData: createBackingFormData(backingFormData),1936    _chunks: chunks,1937    _closed: false,1938    _closedReason: null,1939    _temporaryReferences: temporaryReferences,1940    _rootArrayContexts: new WeakMap(),1941    _arraySizeLimit: arraySizeLimit,1942  };1943  return response;1944}19451946export function resolveField(1947  response: Response,1948  key: string,1949  value: string,1950): void {1951  // Add this field to the backing store.1952  appendBackingEntry(response._formData, key, value);1953  const prefix = response._prefix;1954  if (key.startsWith(prefix)) {1955    const chunks = response._chunks;1956    const id = +key.slice(prefix.length);1957    const chunk = chunks.get(id);1958    if (chunk) {1959      // We were waiting on this key so now we can resolve it.1960      resolveModelChunk(response, chunk, value, id);1961    }1962  }1963}19641965export function resolveFile(response: Response, key: string, file: File): void {1966  // Add this field to the backing store.1967  appendBackingEntry(response._formData, key, file);1968}19691970export opaque type FileHandle = {1971  chunks: Array<Uint8Array>,1972  filename: string,1973  mime: string,1974};19751976export function resolveFileInfo(1977  response: Response,1978  key: string,1979  filename: string,1980  mime: string,1981): FileHandle {1982  return {1983    chunks: [],1984    filename,1985    mime,1986  };1987}19881989export function resolveFileChunk(1990  response: Response,1991  handle: FileHandle,1992  chunk: Uint8Array,1993): void {1994  handle.chunks.push(chunk);1995}19961997export function resolveFileComplete(1998  response: Response,1999  key: string,2000  handle: FileHandle,

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.