> ## Documentation Index
> Fetch the complete documentation index at: https://archie.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# JavaScript SDK

> Use @archie/js-sdk to call your Archie backend from the browser or Node.js. One TypeScript client covers auth, GraphQL, files, realtime, notifications, and REST.

`@archie/js-sdk` is the core TypeScript SDK for the Archie backend. It is isomorphic, so the same client runs in the browser and in Node.js.

Use it when you build a frontend or a server-side script that talks to an Archie project. The React and Node.js SDKs build on this package.

<Note>
  Package: [`@archie/js-sdk` on npm](https://www.npmjs.com/package/@archie/js-sdk).
</Note>

## Install

```bash theme={null}
npm install @archie/js-sdk
# or
pnpm add @archie/js-sdk
```

## Quick start

Create a client with `createClient`, then use its modules.

```typescript theme={null}
import { createClient } from '@archie/js-sdk';

const archie = createClient({
  projectId: 'your-project-uuid',
  apiKey: 'anon your-api-key',
  apiUrl: 'https://your-project.archiecore.com',
  environment: 'master',
});

// Sign in
const { session } = await archie.auth.signIn({
  email: 'user@example.com',
  password: 'secret',
});

// Query data
const { data, error } = await archie.graphql.query('{ users { id email } }');
```

The client exposes these modules:

<CardGroup cols={3}>
  <Card title="archie.auth">Sign up, sign in, sessions, and auth events.</Card>
  <Card title="archie.graphql">Queries and mutations.</Card>
  <Card title="archie.files">Upload, download, and CSV import.</Card>
  <Card title="archie.realtime">GraphQL subscriptions and table channels.</Card>
  <Card title="archie.notifications">Live in-app notifications and read state.</Card>
  <Card title="archie.rest">Custom REST endpoints.</Card>
</CardGroup>

## Configuration

Only `projectId` is required.

| Option | Type | Default | Description |
| - | - | - | - |
| `projectId` | `string` | required | Project UUID. |
| `apiKey` | `string` | `undefined` | API key, sent as-is in the `Authorization` header. |
| `getAccessToken` | `() => string \| null` | `undefined` | External token provider, for example Auth0. |
| `environment` | `string` | `'master'` | Environment name. |
| `apiUrl` | `string` | `'https://api.archie.dev'` | API Manager URL. |
| `authUrl` | `string` | same as `apiUrl` | Auth service URL. |
| `realtimeUrl` | `string` | same as `apiUrl` | WebSocket URL. Auto-converts to `ws://`. |
| `autoRefreshToken` | `boolean` | `true` | Refresh the JWT 60 seconds before expiry. |
| `persistSession` | `boolean` | `true` | Persist the session in storage. |
| `storageAdapter` | `StorageAdapter` | auto-detect | `localStorage` in the browser, memory in Node.js. |
| `headers` | `Record<string,string>` | `{}` | Extra headers on every request. |
| `timeout` | `number` | `30000` | Request timeout in ms. `0` disables it. |
| `retries` | `number` | `0` | Max retry attempts for transient errors. |
| `retryDelay` | `number` | `200` | Initial backoff delay in ms. |
| `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch for testing, proxies, or edge runtimes. |
| `debug` | `boolean` | `false` | Enable console logging. |
| `logger` | `Logger` | noop | Custom logger. Overrides `debug`. |

### Authorization priority

The SDK picks the `Authorization` header in this order:

1. **External token.** If `getAccessToken()` returns a token, the SDK sends `Authorization: Bearer {token}`.
2. **User JWT.** After `signIn`, the SDK sends `Authorization: Bearer {jwt}`.
3. **API key.** The SDK sends `Authorization: {apiKey}` as-is. Examples: `'anon xxxxx'`, `'Bearer eyJhbG...'`.
4. **Nothing.** The SDK sends no `Authorization` header.

### External auth

If your app authenticates users elsewhere, such as Auth0, pass the token through `getAccessToken`. The SDK calls it on every HTTP request and every WebSocket connect. You never need to recreate the client when the token refreshes.

```typescript theme={null}
let currentToken: string | null = null; // your app updates this on Auth0 refresh

const archie = createClient({
  projectId: 'your-project-uuid',
  apiUrl: 'https://your-project.archiecore.com',
  environment: 'master',
  getAccessToken: () => currentToken, // raw token; the SDK adds the `Bearer ` prefix
});
```

<Note>
  Writes such as mutations require a real user identity. Use a user token, not an anonymous `apiKey`.
</Note>

## Auth

Use `archie.auth` for registration, login, session management, auto-refresh, and auth events.

<AccordionGroup>
  <Accordion title="Sign up and confirm">
    ```typescript theme={null}
    const { userId, message } = await archie.auth.signUp({
      email: 'new@user.com',
      password: 'StrongP@ss1',
      firstName: 'Jane', // optional
      lastName: 'Doe', // optional
      roleId: 'role-uuid', // optional
    });

    // After the user receives the verification code by email:
    const { session } = await archie.auth.confirmSignUp({
      email: 'new@user.com',
      code: '123456',
    });
    // The user is now signed in
    ```
  </Accordion>

  <Accordion title="Sign in and sign out">
    ```typescript theme={null}
    const { session } = await archie.auth.signIn({
      email: 'user@example.com',
      password: 'secret',
    });

    console.log(session.user); // { id, email, firstName, lastName, roles }
    console.log(session.accessToken); // JWT
    console.log(session.expiresAt); // Unix timestamp (seconds)

    await archie.auth.signOut(); // clears session and storage, stops auto-refresh
    ```
  </Accordion>

  <Accordion title="Current session and user">
    ```typescript theme={null}
    const session = archie.auth.getSession(); // Session | null
    const user = archie.auth.getUser(); // User | null
    ```

    On page load, the SDK restores the session from storage asynchronously. Wait for it before you check auth state:

    ```typescript theme={null}
    await archie.auth.waitForInit();
    const user = archie.auth.getUser(); // now guaranteed to be loaded
    ```
  </Accordion>

  <Accordion title="Auth state changes">
    `onAuthStateChange` returns an unsubscribe function.

    ```typescript theme={null}
    const unsubscribe = archie.auth.onAuthStateChange((event, session) => {
      // event: 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED' | 'USER_UPDATED'
      if (event === 'SIGNED_IN') {
        console.log('Logged in as', session.user.email);
      }
    });

    unsubscribe();
    ```
  </Accordion>

  <Accordion title="Password recovery">
    ```typescript theme={null}
    // Step 1: request the recovery email
    await archie.auth.recoverPassword({ email: 'user@example.com' });

    // Step 2: reset the password with the code from the email
    await archie.auth.resetPassword({
      email: 'user@example.com',
      code: '123456',
      newPassword: 'NewStr0ngP@ss',
    });
    ```
  </Accordion>

  <Accordion title="Manual refresh and JWKS">
    The SDK refreshes tokens automatically when `autoRefreshToken` is `true`. You can also refresh manually or fetch the JSON Web Key Set for server-side JWT verification.

    ```typescript theme={null}
    const { session } = await archie.auth.refreshSession();
    const { keys } = await archie.auth.getJWKS();
    ```
  </Accordion>

  <Accordion title="Exchange codes">
    `auth.exchangeCode({ code })` redeems a single-use, 60-second code for a normal session as the user the code was issued for, with that user's real roles. Your server issues the code with `POST /auth/admin/exchange-code`, which requires an admin key. Never call that endpoint from a browser.

    ```typescript theme={null}
    const { session } = await archie.auth.exchangeCode({ code });
    session.user.roles; // the user's real role names
    ```

    An exchange session has no refresh token. It ends when its access token expires, which is 15 minutes by default (the project's access-token TTL). With `autoRefreshToken` on, the SDK emits `SESSION_EXPIRING` 60 seconds before expiry. Get a fresh code and call `exchangeCode` again to replace the session in place. If nothing replaces it, `SIGNED_OUT` fires at expiry.

    ```typescript theme={null}
    archie.auth.onAuthStateChange((event) => {
      if (event === 'SESSION_EXPIRING') askTheHostForANewCode();
    });
    ```

    Failures are `AuthError`s. Their `code` is one of `EXCHANGE_CODE_INVALID`, `EXCHANGE_CODE_EXPIRED`, `EXCHANGE_CODE_ALREADY_USED`, `EXCHANGE_USER_NOT_FOUND`, `EXCHANGE_USER_DISABLED`, or `EXCHANGE_RATE_LIMITED`. Branch on `code`, never on the message text.
  </Accordion>
</AccordionGroup>

### Auto-refresh and persistence

When `autoRefreshToken` is `true` (default):

* After sign in, the SDK schedules a token refresh 60 seconds before expiry.
* On success, it emits `TOKEN_REFRESHED` and schedules the next refresh.
* On failure, it calls `signOut()` automatically.

When `persistSession` is `true` (default):

* The SDK saves the session after every sign in and token refresh.
* On client creation, it restores the session from storage.
* If the stored token has expired, it tries a refresh automatically.
* The storage key format is `archie-auth-{projectId}-{environment}`.

## GraphQL

Use `archie.graphql` to run queries and mutations against the `/graphql` endpoint of the Archie API Manager.

`query()` and `mutate()` return `{ data, error }`. They do not throw for GraphQL-level errors.

```typescript theme={null}
interface User {
  id: string;
  email: string;
  name: string;
}

const { data, error } = await archie.graphql.query<{ user: User }>(
  `query GetUser($id: ID!) {
    user(id: $id) { id email name }
  }`,
  { id: 'user-123' },
);

if (error) {
  console.error(error.message, error.code);
} else {
  console.log(data.user);
}
```

```typescript theme={null}
const { data, error } = await archie.graphql.mutate<{ createUser: User }>(
  `mutation CreateUser($input: CreateUserInput!) {
    createUser(input: $input) { id email }
  }`,
  { input: { email: 'new@user.com', name: 'New User' } },
);
```

### Request options

Both methods accept a third parameter with `headers` and `signal`.

```typescript theme={null}
const controller = new AbortController();

const { data } = await archie.graphql.query(
  '{ users { id } }',
  {},
  {
    headers: { 'x-custom': 'value' }, // extra headers for this request
    signal: controller.signal, // AbortSignal for cancellation
  },
);

controller.abort(); // cancel the request
```

### Raw request

Use `request()` when you want the data directly and prefer that errors throw a `GraphQLError`.

```typescript theme={null}
try {
  const data = await archie.graphql.request<{ users: User[] }>({
    query: '{ users { id email } }',
    variables: {},
    operationName: 'GetUsers',
  });
  console.log(data.users); // no { data, error } wrapper
} catch (err) {
  // Throws GraphQLError on failure
}
```

### Response shape

```typescript theme={null}
// Success
{ data: T, error: null }

// GraphQL-level errors (partial data may exist)
{ data: T | null, error: GraphQLError }

// Network or HTTP error
{ data: null, error: ArchieError }
```

## Files

Use `archie.files` to upload, download, and manage files through GraphQL multipart uploads.

```typescript theme={null}
const input = document.querySelector<HTMLInputElement>('#fileInput');
const file = input.files[0];

const { url, fileId } = await archie.files.upload(file, {
  filename: 'photo.jpg',
  contentType: 'image/jpeg',
  providerType: 's3', // optional: storage provider hint
  onProgress: (pct) => console.log(`${pct}% uploaded`), // optional
});
```

`upload` accepts a `File`, a `Blob`, or a `Uint8Array`. The `Uint8Array` form also works in Node.js.

```typescript theme={null}
const blob = new Blob(['Hello, world!'], { type: 'text/plain' });
await archie.files.upload(blob, { filename: 'hello.txt' });

const bytes = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]);
await archie.files.upload(bytes, { filename: 'hello.bin' });
```

<AccordionGroup>
  <Accordion title="Import a CSV into a table">
    ```typescript theme={null}
    const csvFile = document.querySelector<HTMLInputElement>('#csvInput').files[0];

    const { result } = await archie.files.uploadCsv(csvFile, {
      tableName: 'products',
      transactionality: true, // optional: roll back everything on error
      limit: 1000, // optional: max rows to import
    });

    console.log(result.success); // boolean
    console.log(result.rowsImported); // number
    console.log(result.errors); // import-level errors
    ```
  </Accordion>

  <Accordion title="Download a file">
    ```typescript theme={null}
    const blob = await archie.files.download('file-id-123');

    // In the browser, trigger a download:
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'my-file.pdf';
    a.click();
    URL.revokeObjectURL(url);
    ```
  </Accordion>

  <Accordion title="Get a file URL">
    `getUrl` builds the URL locally. It makes no network request.

    ```typescript theme={null}
    const url = archie.files.getUrl('file-id-123');
    // "https://example.platform.com/files?id=file-id-123"
    ```
  </Accordion>
</AccordionGroup>

## Realtime

Use `archie.realtime` for WebSocket subscriptions. It uses the `graphql-transport-ws` protocol, connects lazily, and reconnects automatically.

```typescript theme={null}
const sub = archie.realtime.subscribe<{ orderUpdated: Order }>(
  `subscription {
    orderUpdated { id status total }
  }`,
  {}, // variables
  {
    onData: (data) => console.log('Order updated:', data.orderUpdated),
    onError: (error) => console.error('Subscription error:', error.message),
    onComplete: () => console.log('Subscription ended'),
  },
);

sub.unsubscribe(); // stop listening
```

### Channels

The channel API is a shorter way to listen to table-level events. Event names are `INSERT`, `UPDATE`, `DELETE`, and `*`.

```typescript theme={null}
const channel = archie.realtime.channel('orders');

channel
  .on('INSERT', (payload) => console.log('New order:', payload))
  .on('UPDATE', (payload) => console.log('Order updated:', payload))
  .on('DELETE', (payload) => console.log('Order deleted:', payload))
  .on('*', (payload) => console.log('Any event:', payload));

channel.subscribe(); // start listening
channel.unsubscribe(); // stop listening
```

### Connection state

```typescript theme={null}
console.log(archie.realtime.currentState);
// 'DISCONNECTED' | 'CONNECTING' | 'CONNECTED' | 'RECONNECTING'

const unsubscribe = archie.realtime.onConnectionStateChange((state) => {
  if (state === 'RECONNECTING') showReconnectingBanner();
});
```

When the connection drops, the SDK reconnects with exponential backoff: 1s, 2s, 4s, 8s, 16s, then 30s at most. The counter resets after a successful `connection_ack`. The connection closes when the last subscription is unsubscribed. On `TOKEN_REFRESHED`, the SDK reconnects with the new credentials.

## Notifications

Use `archie.notifications` to receive live in-app notifications and manage read state. `receiverId` is the authenticated user's id, available from `archie.auth.getUser()?.id`.

```typescript theme={null}
const receiverId = archie.auth.getUser()?.id ?? '';

const channel = archie.notifications.subscribe(receiverId, {
  onError: (err) => console.error('Notification channel error:', err),
});

channel
  .on('welcome', (n) => toast(n.actionText ?? 'Welcome!')) // filter by notification key
  .on('*', () => badge.increment()); // every notification

channel.unsubscribe(); // clean up on unmount or sign-out
```

### List, count, and mark as read

```typescript theme={null}
const { items, count, totalCount } = await archie.notifications.list(receiverId, {
  unreadOnly: true,
  first: 20,
  skip: 0,
  orderBy: { createdAt: 'DESC' },
});

const unread = await archie.notifications.getUnreadCount(receiverId); // number

const updated = await archie.notifications.markAsRead(notificationId); // updated Notification
const markedCount = await archie.notifications.markAllAsRead(receiverId); // number marked
```

| Option | Type | Description |
| - | - | - |
| `unreadOnly` | `boolean` | Return only unread notifications. Default `false`. |
| `first` | `number` | Page size. |
| `skip` | `number` | Offset for pagination. |
| `orderBy` | `Record<string, 'ASC'\|'DESC'>` | Field-to-direction map. Default `{ createdAt: 'DESC' }`. |
| `createdBefore` | `string` | Keyset cursor. Returns rows older than this ISO `createdAt`. |
| `excludeIds` | `string[]` | Tie-safe keyset. Ids already loaded at the `createdBefore` timestamp. |
| `includeTotal` | `boolean` | Request `count` and `totalCount`. Default `true`. `false` skips the COUNT. |

<Accordion title="Paginate history (infinite scroll)">
  `list` supports keyset pagination through `createdBefore`. Pass the `createdAt` of the oldest row you already have to fetch the next older page.

  ```typescript theme={null}
  const first = await archie.notifications.list(receiverId, { first: 20 });
  const oldest = first.items[first.items.length - 1];

  const older = await archie.notifications.list(receiverId, {
    first: 20,
    orderBy: { createdAt: 'DESC' },
    createdBefore: oldest.createdAt,
    // Tie-safe: pass ids already loaded at the cursor's exact createdAt.
    excludeIds: first.items.filter((n) => n.createdAt === oldest.createdAt).map((n) => n.id),
    // Skip the COUNT aggregate on deep pages.
    includeTotal: false,
  });

  const hasMore = older.items.length === 20; // full page means more may remain
  ```

  The `@archie/react-sdk` `useNotifications` hook does all of this for you. Use the raw options when you paginate outside React.
</Accordion>

### Notification shape

All fields are camelCase. The SDK normalizes backend inconsistencies before it delivers notifications to you.

```typescript theme={null}
interface Notification {
  id: string;
  receiverId: string;
  notificationKey: string; // event key, for example 'welcome'
  notificationType?: string | null;
  notificationTypeId?: string | null;
  actionText?: string | null; // CTA label
  actionUrl?: string | null; // CTA destination; validate before rendering
  isRead: boolean;
  readAt?: string | null; // ISO date
  expiresAt?: string | null; // ISO date
  metadata?: Record<string, unknown> | null;
  params?: Record<string, unknown> | null; // template variables
  dedupKey?: string | null;
  createdAt: string; // ISO date
  updatedAt?: string | null;
}
```

<Warning>
  The Archie backend enforces authorization, not the SDK. `receiverId` and notification `id` are request parameters, not a client-side trust boundary. The backend scopes all reads and writes to the authenticated principal.

  Treat `actionUrl` as untrusted data. Before you render it as a link, check that the scheme is `http:` or `https:`. Reject `javascript:` and `data:` URLs, which enable stored XSS.

  Treat `metadata` and `params` as untrusted too. If you deep-merge them into other objects, use a merge that guards against `__proto__`, `constructor`, and `prototype` keys.
</Warning>

```typescript theme={null}
function safeActionUrl(url: string | null | undefined): string | undefined {
  if (!url) return undefined;
  try {
    const { protocol } = new URL(url);
    if (protocol === 'http:' || protocol === 'https:') return url;
  } catch {
    // not a valid URL
  }
  return undefined;
}
```

## REST

Use `archie.rest` to call custom REST APIs created through the Archie gateway. The SDK injects the standard headers (`x-project-id`, `authorization`, `environment`) for you.

```typescript theme={null}
const products = await archie.rest.get<Product[]>('/api/products', {
  params: { category: 'electronics', limit: '10' }, // GET /api/products?category=electronics&limit=10
});

const created = await archie.rest.post<Product>('/api/products', { name: 'Widget', price: 9.99 });
await archie.rest.put('/api/products/123', { name: 'Updated Widget', price: 12.99 });
await archie.rest.patch('/api/products/123', { price: 14.99 });
await archie.rest.delete('/api/products/123');
```

Every method accepts an options object with `headers`, `params`, and `signal`. REST methods throw `ArchieError` on non-2xx responses.

```typescript theme={null}
try {
  await archie.rest.get('/api/products/missing');
} catch (err) {
  if (err instanceof ArchieError) {
    console.log(err.status); // 404
    console.log(err.code); // 'HTTP_404'
    console.log(err.message); // server-provided message
  }
}
```

## Error handling

All SDK errors extend `ArchieError`.

| Class | When | Key properties |
| - | - | - |
| `ArchieError` | Base class for all SDK errors. | `message`, `code`, `status`, `details`, `hint` |
| `AuthError` | Any failure of an `auth.*` call. | Same as `ArchieError`. |
| `GraphQLError` | GraphQL response errors. | `path`, `locations`, plus `ArchieError` props. |
| `NetworkError` | Network issues: timeout, DNS, disconnected. | `hint`, for example "Check your internet...". |

```typescript theme={null}
import { ArchieError, AuthError, GraphQLError, NetworkError } from '@archie/js-sdk';

try {
  await archie.auth.signIn({ email: 'test@test.com', password: 'wrong' });
} catch (err) {
  if (err instanceof AuthError) {
    console.log(err.code); // 'AUTH_INVALID_CREDENTIALS', or 'AUTH_ERROR'
    console.log(err.status); // 401
    console.log(err.message); // safe to show the user
  } else if (err instanceof NetworkError) {
    // Could not reach the service. Not a credential problem.
    console.log(err.hint);
  } else if (err instanceof ArchieError) {
    // The service answered without a verdict: 5xx, timeout, gateway page.
    console.log(err.code, err.status, err.details); // for example 'HTTP_500' or 'TIMEOUT'
  }
}
```

GraphQL errors from `query()` and `mutate()` come back in the response:

```typescript theme={null}
const { data, error } = await archie.graphql.query('{ invalidField }');

if (error) {
  console.log(error.message); // 'Cannot query field "invalidField"'
  console.log(error.code); // 'GRAPHQL_VALIDATION_FAILED'
  console.log(error.path); // ['invalidField']
  console.log(error.locations); // [{ line: 1, column: 3 }]
}
```

### Auth error codes

A failed `auth.*` call throws one of two families. Handle both.

* **The server reached a verdict.** Examples: wrong password, unverified email, expired code. The SDK always throws an `AuthError`, even when the backend reports the rejection as an HTTP 200 with a GraphQL `errors[]` body. `err.message` carries the backend's wording and is safe to show the user.
* **The service could not answer.** Examples: network failure, timeout, a 5xx or gateway page. These stay `NetworkError` or `ArchieError`. Treat them as unavailability ("try again in a moment"), not as a credential problem.

Do not test only for `AuthError`. A login form that does goes silent whenever the auth service is down.

`err.code` carries the `extensions.code` value from the server. When the server sends none, it falls back to `AUTH_ERROR`.

| Code | Description |
| - | - |
| `AUTH_INVALID_CREDENTIALS` | Wrong email or password. |
| `AUTH_EMAIL_EXISTS` | Email already registered. |
| `AUTH_EMAIL_NOT_VERIFIED` | Email confirmation required. |
| `AUTH_ACCOUNT_LOCKED` | Account temporarily locked. |
| `AUTH_INVALID_CODE` | Invalid verification or recovery code. |
| `AUTH_TOKEN_EXPIRED` | Access token has expired. |
| `AUTH_TOKEN_INVALID` | Malformed or revoked token. |
| `AUTH_NOT_CONFIGURED` | Auth not enabled for the project. |
| `AUTH_WEAK_PASSWORD` | Password does not meet strength requirements. |
| `AUTH_ERROR` | Auth failure the server sent no code for. |
| `AUTH_INVALID_RESPONSE` | Auth service returned an unusable payload. |

### Automatic 401 retry

When a request returns 401, the SDK refreshes the token and retries the original request. If the refresh fails, it signs the user out and throws the error. Your code does not need to handle this.

## Retry and timeout

```typescript theme={null}
const archie = createClient({
  projectId: 'your-project-uuid',
  retries: 3, // retry up to 3 times on transient errors
  retryDelay: 200, // 200ms, then 400ms, then 800ms (with jitter)
  timeout: 15_000, // abort requests after 15 seconds
});
```

The SDK retries on these conditions:

| Condition | Status codes or errors |
| - | - |
| Server errors | `500`, `502`, `503`, `504` |
| Rate limiting | `429` (respects the `Retry-After` header) |
| Network failures | `TypeError` (DNS, connectivity) |
| Timeouts | `TIMEOUT` errors |

Non-retryable errors (`400`, `401`, `403`, `404`, `409`, and similar) throw immediately.

Backoff is exponential with jitter: `delay = min(baseDelay × 2^(attempt-1) + random_jitter, 30s)`.

For timeouts:

* When `timeout > 0`, each individual request, including retries, aborts after `timeout` milliseconds.
* The SDK throws `ArchieError` with `code: 'TIMEOUT'` and `status: 408`.
* Set `timeout: 0` to disable the timeout.
* A user-provided `AbortSignal` takes precedence over the timeout.

## Custom fetch

Inject your own `fetch` for testing, proxies, or edge runtimes such as Cloudflare Workers and Deno.

```typescript theme={null}
const archie = createClient({
  projectId: '...',
  fetch: async (url, init) => {
    console.log('→', init?.method, url);
    const response = await globalThis.fetch(url, init);
    console.log('←', response.status);
    return response;
  },
});
```

## Health check

`ping()` returns `true` when the API responds with a `2xx` status and `false` otherwise. It never throws.

```typescript theme={null}
const isUp = await archie.ping();
```

## Storage adapters

The SDK persists sessions through a `StorageAdapter`. The default is `localStorage` in the browser and memory in Node.js.

```typescript theme={null}
interface StorageAdapter {
  getItem(key: string): string | null | Promise<string | null>;
  setItem(key: string, value: string): void | Promise<void>;
  removeItem(key: string): void | Promise<void>;
}
```

Two adapters are built in. Use `MemoryStorage` for SSR, tests, or environments without `localStorage`.

```typescript theme={null}
import { BrowserLocalStorage, MemoryStorage } from '@archie/js-sdk';

const archie = createClient({
  projectId: '...',
  storageAdapter: new MemoryStorage(), // or new BrowserLocalStorage()
});
```

You can also write your own, for example with AsyncStorage on React Native:

```typescript theme={null}
import AsyncStorage from '@react-native-async-storage/async-storage';

const archie = createClient({
  projectId: '...',
  storageAdapter: {
    getItem: (key) => AsyncStorage.getItem(key),
    setItem: (key, value) => AsyncStorage.setItem(key, value),
    removeItem: (key) => AsyncStorage.removeItem(key),
  },
});
```

## Logging

Two logger constants are available for the `logger` option. `consoleLogger` writes to `console.debug`, `console.info`, `console.warn`, and `console.error`. `noopLogger` silences all logging and is the default.

```typescript theme={null}
import { consoleLogger, noopLogger } from '@archie/js-sdk';

const archie = createClient({ projectId: '...', logger: consoleLogger });
```

## TypeScript types

The package exports all of its types.

```typescript theme={null}
import type { ArchieClientOptions, Session, User } from '@archie/js-sdk';
```

<AccordionGroup>
  <Accordion title="Auth types">
    ```typescript theme={null}
    import type {
      AuthSignUpParams,
      AuthSignInParams,
      AuthConfirmParams,
      AuthRecoverParams,
      AuthResetPasswordParams,
      AuthEvent, // 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED' | 'USER_UPDATED'
      AuthEventCallback,
    } from '@archie/js-sdk';
    ```
  </Accordion>

  <Accordion title="GraphQL types">
    ```typescript theme={null}
    import type {
      GraphQLResponse, // { data: T | null; error: ArchieError | null }
      GraphQLRequestOptions, // { headers?, signal? }
      GraphQLRawRequest, // { query, variables?, operationName? }
    } from '@archie/js-sdk';
    ```
  </Accordion>

  <Accordion title="File types">
    ```typescript theme={null}
    import type {
      FileUploadOptions, // { filename?, contentType?, providerType?, onProgress? }
      FileUploadResult, // { url: string; fileId: string }
      CsvUploadOptions, // { tableName, transactionality?, limit? }
    } from '@archie/js-sdk';
    ```
  </Accordion>

  <Accordion title="Realtime types">
    ```typescript theme={null}
    import type {
      Subscription, // { unsubscribe: () => void }
      SubscriptionCallbacks, // { onData, onError?, onComplete? }
      RealtimeEvent, // 'INSERT' | 'UPDATE' | 'DELETE' | '*'
      RealtimeChannel, // { on, subscribe, unsubscribe }
      ConnectionState, // 'CONNECTING' | 'CONNECTED' | 'DISCONNECTED' | 'RECONNECTING'
      ConnectionStateCallback,
    } from '@archie/js-sdk';
    ```
  </Accordion>

  <Accordion title="REST types">
    ```typescript theme={null}
    import type {
      RestRequestOptions, // { headers?, params?, signal? }
    } from '@archie/js-sdk';
    ```
  </Accordion>

  <Accordion title="Module interfaces and classes">
    Use the interfaces for dependency injection and testing. Use the concrete classes for `instanceof` checks or advanced typing.

    ```typescript theme={null}
    import type {
      IAuthModule,
      IGraphQLModule,
      IFileModule,
      IRealtimeModule,
      IRestModule,
      IHttpClient,
      Logger,
      TokenAccessor,
      StorageAdapter,
    } from '@archie/js-sdk';

    import { AuthModule, GraphQLModule, FileModule, RealtimeModule, RestModule } from '@archie/js-sdk';
    ```
  </Accordion>
</AccordionGroup>

## Links

* [`@archie/js-sdk` on npm](https://www.npmjs.com/package/@archie/js-sdk)
* License: MIT


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.