> For the complete documentation index, see [llms.txt](/llms.txt)

# React Native

Glotto generates a first-class React-Native SDK — not a re-skin of the web client. It
shares the same `GlottoIR` as the TypeScript target but emits an independent, Hermes-safe,
Metro-friendly package.

## Quickstart

```bash
npm install @your-org/petstore
```

```tsx
import { Client } from '@your-org/petstore';

// the token comes from your secure store rather than a build-time env var
const client = new Client({ token });

// a single call
const pet = await client.pets.createPet({ name: 'Rex' });

// pagination is an async iterator, same surface as the TypeScript SDK
for await (const pet of client.pets.listPets()) {
  console.log(pet.name);
}
```

## Secure token storage

The bearer token can be backed by a secure-storage adapter instead of memory:

- `expo-secure-store` (Expo)
- Keychain (`react-native-keychain`)
- MMKV (`react-native-mmkv`)

You inject the adapter; the SDK reads/writes the token through it.

## Resilient retries on mobile

The [retry policy](/docs/retries) becomes network-aware when you inject the optional deps:

- **NetInfo** (`@react-native-community/netinfo`) — pause retries while offline, resume on reconnect.
- **AppState** — pause retry backoff while the app is backgrounded, resume on foreground. The same injected `AppState` also pauses long-lived `waitFor` polling and SSE/NDJSON streaming while backgrounded (see below), so nothing does work off-screen.

Both are optional: no NetInfo/AppState, no hard dependency.

## Streaming & hooks

- **Streaming** uses an optional `ReadableStream` polyfill exported from the `/streams`
  subpath, plus NDJSON via async generators. A frame whose payload isn't valid JSON surfaces a
  parse error to your loop (never a raw string mistyped as your model); declare terminal
  sentinels like `data: [DONE]` via `streaming.on_event`. See [Streaming](/docs/streaming).
- **Backgrounding a stream**: inject `AppState` on the `Client` (the same option as the retry
  pause) and a long-lived SSE/NDJSON stream pauses at the top of its read loop while the app is
  backgrounded — it stops consuming and yielding events off-screen and resumes on foreground.
  Caller `abort` still tears the stream down immediately. No `AppState`, no behavior change.
- **Streaming on Expo**: React Native's built-in `fetch` buffers responses, so an SSE/NDJSON
  call over it fails with an actionable error instead of a silently empty stream. On Expo,
  opt into streaming with the `/expo` subpath's typed `expo/fetch` adapter:

  ```ts
  import { expoStreamingFetch } from '<your-sdk>/expo';

  const client = new Client({ fetch: expoStreamingFetch });
  ```

  The `/expo` subpath is the only module that imports Expo — bare React Native apps never
  resolve it (pass any other streaming-capable `fetch` the same way).
- **TanStack Query 5 hooks** are available from the `/hooks` subpath for idiomatic data
  fetching with `useQuery`/`useInfiniteQuery`.

## Testing with Jest

The SDK package is ESM-first, so Jest's default `node_modules` ignore needs an allowance
for it — with either the `react-native` or the `jest-expo` preset:

```js
// jest.config.js
module.exports = {
  preset: 'jest-expo', // or 'react-native'
  transformIgnorePatterns: [
    'node_modules/(?!((jest-)?react-native|@react-native(-community)?|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|<your-sdk>)/)',
  ],
};
```

Because every native seam is injected (NetInfo, AppState, secure storage, `fetch`), unit
tests pass plain objects — no native-module mocks are required to exercise the client.

## Unknown response fields

Your API can add a response field without it being a breaking change — but a generated interface gives you no typed way to reach it. The generated
models keep it instead: a field the SDK wasn't generated from is retained on decode, readable
through an accessor, and written back out when the model is re-serialized.

```ts
const pet = await client.pets.createPet(body);

const extra = petExtraFields(pet);
if ('species' in extra) {
  console.log(extra.species);
}
```

Each model gets its own reader — `petExtraFields(pet)`, `tagExtraFields(tag)` — typed to that
model, so passing the wrong one is a compile error.

As in the TypeScript SDK, the data was always retained at runtime (the decode is a cast); the
readers are what make it reachable and keep it guaranteed rather than incidental.

The retained fields are read-only by design. To *send* a field your spec doesn't model yet, use the
per-call extra-body escape hatch rather than writing to the retained bag.

## Platform integration

- **Hermes-compatible** output (no `eval`, no `Function` constructor, no `Proxy`).
- An optional **Expo config plugin** wires the OAuth deep-link scheme and the iOS keychain
  entitlement, and can pin the app's JS engine via its `jsEngine` prop. Glotto emits it when the
  API's auth capabilities need it. An API without OAuth or bearer-style/secure-storage auth
  capability—for example, one using only custom or API-key auth—does not receive a `./plugin`
  subpath by default. To opt any React Native target in explicitly, set
  `targets.react_native.expo_plugin: true`, then import the public subpath in your Expo config:

  ```ts
  import withGlotto from '<your-sdk>/plugin';
  ```
- **AbortSignal** is never polyfilled — the SDK detects and warns if a polyfill is installed.
- A **Metro-friendly** package shape: side-effect-free, ESM-first, tree-shakable per
  resource, with the manifest's `react-native` field pointing at shipped TypeScript source
  so Metro consumes it directly.

## Pages, request controls, and files

Paginated methods also expose [manual pages](/docs/pagination#fetch-one-page),
so you can inspect one response and request its successor explicitly. Request
overrides apply to the initial call and to each requested next page; see
[Retries & timeouts](/docs/retries) for precedence and cancellation.

[Binary downloads](/docs/file-transfers) return an owned byte response with
metadata, a bounded read helper, and incremental consumption. Close the response
when you stop early. Your generated README demonstrates these operations with
your API's names and the language's native calling conventions.
