Repository: remix-run/react-router
File: packages/react-router/lib/dom/ssr/single-fetch.tsx (TypeScript)

Code before:
import {
  ErrorResponseImpl,
  isRouteErrorResponse,
  redirect,
  data,
  stripBasename,
} from "../../router/utils";
import { createRequestInit } from "./data";
import type { AssetsManifest, EntryContext } from "./entry";
import { escapeHtml } from "./markup";
import type { RouteModules } from "./routeModules";
import invariant from "./invariant";

export const SingleFetchRedirectSymbol = Symbol("SingleFetchRedirect");

export type SingleFetchRedirectResult = {
  redirect: string;
  status: number;
  revalidate: boolean;
  reload: boolean;
  replace: boolean;
};

// Shared/serializable type used by both turbo-stream and RSC implementations
type DecodedSingleFetchResults =
  | { routes: { [key: string]: SingleFetchResult } }
  | { redirect: SingleFetchRedirectResult };

// This and SingleFetchResults are only used over the wire, and are converted to
// AgnosticSingleFetchResults in `fethAndDecode`.  This way turbo-stream/RSC
// can use the same `unwrapSingleFetchResult` implementation
export type SingleFetchResult =
  | { data: unknown }
  | { error: unknown }
  | SingleFetchRedirectResult;

export type SingleFetchResults =
  | { [key: string]: SingleFetchResult }
  | { [SingleFetchRedirectSymbol]: SingleFetchRedirectResult };

interface StreamTransferProps {
  context: EntryContext;
  identifier: number;
  reader: ReadableStreamDefaultReader<Uint8Array>;
  textDecoder: TextDecoder;
  nonce?: string;
}

// some status codes are not permitted to have bodies, so we want to just
// treat those as "no data" instead of throwing an exception.
// 304 is not included here because the browser should fill those responses

Review comment (suggestion):
`fetchAndDecode` will now return this shape which can be the common payload for turbo-stream and RSC.  `unwrapSingleFetchResult` now operates on this so that implementation can be shared.

Code after:
import {
  ErrorResponseImpl,
  isRouteErrorResponse,
  redirect,
  data,
  stripBasename,
} from "../../router/utils";
import { createRequestInit } from "./data";
import type { AssetsManifest, EntryContext } from "./entry";
import { escapeHtml } from "./markup";
import invariant from "./invariant";

export const SingleFetchRedirectSymbol = Symbol("SingleFetchRedirect");

export type SingleFetchRedirectResult = {
  redirect: string;
  status: number;
  revalidate: boolean;
  reload: boolean;
  replace: boolean;
};

// Shared/serializable type used by both turbo-stream and RSC implementations
type DecodedSingleFetchResults =
  | { routes: { [key: string]: SingleFetchResult } }
  | { redirect: SingleFetchRedirectResult };

// This and SingleFetchResults are only used over the wire, and are converted to
// DecodedSingleFetchResults in `fetchAndDecode`.  This way turbo-stream/RSC
// can use the same `unwrapSingleFetchResult` implementation.
export type SingleFetchResult =
  | { data: unknown }
  | { error: unknown }
  | SingleFetchRedirectResult;

export type SingleFetchResults =
  | { [key: string]: SingleFetchResult }
  | { [SingleFetchRedirectSymbol]: SingleFetchRedirectResult };

interface StreamTransferProps {
  context: EntryContext;
  identifier: number;
  reader: ReadableStreamDefaultReader<Uint8Array>;
  textDecoder: TextDecoder;
  nonce?: string;
}

// Some status codes are not permitted to have bodies, so we want to just
// treat those as "no data" instead of throwing an exception:
//   https://datatracker.ietf.org/doc/html/rfc9110#name-informational-1xx
//   https://datatracker.ietf.org/doc/html/rfc9110#name-204-no-content