Repository: expo/expo
File: packages/expo/src/winter/fetch/fetch.ts (TypeScript)

Code before:
import { ExpoFetchModule } from './ExpoFetchModule';
import { FetchError } from './FetchErrors';
import { FetchResponse, type AbortSubscriptionCleanupFunction } from './FetchResponse';
import { NativeRequest, NativeRequestInit } from './NativeRequest';
import { normalizeBodyInitAsync, normalizeHeadersInit, overrideHeaders } from './RequestUtils';
import type { FetchRequestInit, FetchRequestLike } from './fetch.types';

/** Returns if `input` is a Request object */
const isRequest = (input: any): input is Request =>
  input != null && typeof input === 'object' && 'body' in input;

// TODO(@kitten): Do we really want to use our own types for web standards?
export async function fetch(
  input: string | URL | FetchRequestLike,
  init?: FetchRequestInit
): Promise<FetchResponse> {
  const initFromRequest = isRequest(input);
  const url = initFromRequest ? input.url : input;
  const body = initFromRequest ? input.body : init?.body || null;
  const signal = initFromRequest ? input.signal : init?.signal || undefined;
  const redirect = initFromRequest ? input.redirect : init?.redirect;
  const method = initFromRequest ? input.method : init?.method;
  const credentials = initFromRequest ? input.credentials : init?.credentials;

  let headers = normalizeHeadersInit(initFromRequest ? input.headers : init?.headers);

  let abortSubscription: AbortSubscriptionCleanupFunction | null = null;

  const response = new FetchResponse(() => {
    abortSubscription?.();
  });

  const request = new ExpoFetchModule.NativeRequest(response) as NativeRequest;

  const { body: requestBody, overriddenHeaders } = await normalizeBodyInitAsync(body);
  if (overriddenHeaders) {
    headers = overrideHeaders(headers, overriddenHeaders);
  }

  const nativeRequestInit: NativeRequestInit = {

Review comment (suggestion):
[🏗️ **design**] This check is too loose — any object with a `body` property will match (e.g., a plain `{ body: 'data', url: '...' }` config object). The Fetch spec identifies `Request` by the presence of `url`, `method`, and `body` together. A more robust check:

```ts
const isRequest = (input: any): input is FetchRequestLike =>
  input != null && typeof input === 'object' && 'url' in input && 'method' in input && 'body' in input;
```

Also note the type predicate says `input is Request` (the global `Request`), but `FetchRequestLike` is the type that actually represents what the function accepts. Consider using `input is FetchRequestLike` instead.

Code after:
import { ExpoFetchModule } from './ExpoFetchModule';
import { FetchError } from './FetchErrors';
import { FetchResponse, type AbortSubscriptionCleanupFunction } from './FetchResponse';
import { NativeRequest, NativeRequestInit } from './NativeRequest';
import {
  normalizeBodyInitAsync,
  normalizeHeadersInit,
  overrideHeaders,
  normalizeMethod,
} from './RequestUtils';
import type { FetchRequestInit, FetchRequestLike } from './fetch.types';

/** Returns if `input` is a Request object */
const isRequest = (input: any): input is FetchRequestLike =>
  input != null && typeof input === 'object' && 'body' in input;

// TODO(@kitten): Do we really want to use our own types for web standards?
export async function fetch(
  input: string | URL | FetchRequestLike,
  init?: FetchRequestInit
): Promise<FetchResponse> {
  const initFromRequest = isRequest(input);
  const url = initFromRequest ? input.url : input;
  const body = init?.body ?? (initFromRequest ? input.body : null);
  const signal = init?.signal ?? (initFromRequest ? input.signal : undefined);
  const redirect = init?.redirect ?? (initFromRequest ? input.redirect : undefined);
  const method = init?.method ?? (initFromRequest ? input.method : undefined);
  const credentials = init?.credentials ?? (initFromRequest ? input.credentials : undefined);

  let headers = normalizeHeadersInit(
    init?.headers ?? (initFromRequest ? input.headers : undefined)
  );

  let abortSubscription: AbortSubscriptionCleanupFunction | null = null;

  const response = new FetchResponse(() => {
    abortSubscription?.();
  });

  const request = new ExpoFetchModule.NativeRequest(response) as NativeRequest;