Skip to content Documentation index for agents (llms.txt)
Glotto Beta
Get started

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

npm install @your-org/petstore
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 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.

  • 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:

    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:

// 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.

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:

    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, 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 for precedence and cancellation.

Binary downloads 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.