# API Reference

This reference describes the public application SDK contract for `@tidbcloud/auth-js`.

The SDK provides TiDB-native Supabase-like ergonomics for published Data APIs and Auth-aware request execution. It does not provide `supabase-js`, PostgREST, PostgreSQL, PostgreSQL RLS, or PostgreSQL extension compatibility.

## Entry Points

### `@tidbcloud/auth-js`

Primary application entry.

```ts
import {
  createClient,
  TidbClient,
  packageInfo
} from '@tidbcloud/auth-js';
```

Public exports:

- `createClient`
- `TidbClient`
- `packageInfo`
- `classifyDataApiErrorCode`
- capability helpers such as `getCapability`, `isNativeCapability`, `isDeferredCapability`, and `isUnsupportedCapability`
- Data API type helpers such as `GenericDatabase`, `TableName`, `RowOf`, `InsertOf`, `UpdateOf`, `ColumnName`, and `RequestDescriptor`
- client option types such as `TidbClientOptions`, `TidbCloudClientOptions`, and `RuntimeName`

### `@tidbcloud/auth-js/browser`

Browser runtime policy entry.

```ts
import { browserRuntime, browserSecurityPolicy } from '@tidbcloud/auth-js/browser';
```

Browser policy:

- accepts public client keys only
- must not accept, store, log, broadcast, or bundle service keys
- must not import server-only helpers

### `@tidbcloud/auth-js/server`

Server runtime policy entry.

```ts
import { serverRuntime, serverSecurityPolicy } from '@tidbcloud/auth-js/server';
```

Server policy:

- does not accept Service Keys for public Data or Schema APIs
- keeps runtime checks separate from browser and edge entry points
- uses the root `createClient()` API with a public Client Key and optional user JWT for application requests

### `@tidbcloud/auth-js/edge`

Edge runtime policy entry.

```ts
import { edgeRuntime, edgeSecurityPolicy } from '@tidbcloud/auth-js/edge';
```

Edge policy:

- no Node-only runtime assumptions
- no implicit process-global persistent session storage
- caller-provided storage and fetch behavior must be explicit

### `@tidbcloud/auth-js/schema`

Schema discovery entry.

The production contract is to derive application-visible schema metadata from published Data API metadata such as `/schema` or `/doc`, not direct TiDB introspection from application SDK code.

### `@tidbcloud/auth-js/typegen`

Type generation entry.

The production contract is to generate TypeScript database types from published metadata and service fixtures. Type generation must not require raw SQL, DDL, or direct database credentials.

## Client Creation

### `createClient<Database>(url, clientKey, options?)`

Creates a typed TiDB Cloud application client.

```ts
const tidb = createClient<Database>('https://api.example.test', 'public-client-key', {
  global: {
    fetch: customFetch,
    headers: {
      'X-App-Version': 'web-2026.06.25'
    },
    timeoutMs: 10_000,
    retry: { retries: 3 }
  },
  auth: {
    getAccessToken: async () => userAccessToken,
    autoRefreshToken: true
  },
  data: {
    pathStyle: 'table'
  }
});
```

Parameters:

- `url`: base service URL.
- `clientKey`: public client key for application access.
- `options.global.fetch`: optional Fetch-compatible implementation.
- `options.global.headers`: optional headers applied to every Auth, Data API, and schema request (sanitized — credential-bearing headers such as `authorization` and service-key headers are stripped, and `apikey`/`Authorization` cannot be overridden).
- `options.global.timeoutMs`: optional per-request timeout in milliseconds. Aborts the request and returns a `timeout` error when exceeded.
- `options.global.retry`: optional retry policy override (`Partial<{ retries, baseDelayMs, maxDelayMs, retryStatuses }>`), or `false` to disable retries. Only idempotent methods (`GET`/`HEAD`/`OPTIONS`) on retryable statuses (`408`, `429`, `5xx`) are retried; mutations are never retried. Defaults to 2 retries with exponential backoff.
- `options.global.logger`: optional structured logger receiving redacted request/response events.
- `options.global.hooks`: optional request lifecycle hooks (`beforeRequest`/`afterResponse`/`onError`) receiving redacted context. A throwing hook never rejects a request — it is isolated and logged.
- `options.auth.getAccessToken`: optional callback returning the current user JWT.
- `options.auth.persistSession`: persist the session to storage. Defaults to `true`.
- `options.auth.autoRefreshToken`: proactively refresh the session before expiry (see [Session Lifecycle](#session-lifecycle)). Defaults to `true`.
- `options.auth.refreshWindowSeconds`: refresh the token when it is within this many seconds of expiry. Defaults to `60`.
- `options.auth.detectSessionInUrl`: run OAuth callback recovery during construction in browsers. Defaults to `true`.
- `options.auth.broadcast` / `options.auth.storageEvent`: cross-tab session sync channels, both enabled by default (set to `false` to disable).
- `options.auth.storageKey`: override the storage key for the persisted session.
- `options.data.pathStyle`: Data API path style. Defaults to `table` (canonical `/table/<table>` route). `rest-v1` is a legacy opt-in that builds `/rest/v1/<table>`, which the backend has retired (returns HTTP 501).
- `options.data.schema`: optional published schema metadata used for local action/column guards.

Returns:

- `TidbClient<Database>`

Security:

- Public Data and Schema requests must pass a public Client Key.
- A user JWT is optional for anonymous access and required when the caller needs authenticated-user policy context.
- `X-Service-Key` is rejected by the Public Data/Schema service and is never generated by this SDK.

## `TidbClient`

### `client.from(table)`

Creates a table-scoped Data API builder.

```ts
const tasks = tidb.from('tasks');
```

`table` is type-checked when a `Database` type is supplied.

### `client.schema`

Discovers published schema and contract metadata using the public client key and current user access token.

```ts
await tidb.schema.getSchema();
await tidb.schema.getTableSchema('tasks');
await tidb.schema.getOpenAPIDocument();
await tidb.schema.getCapabilities();
await tidb.schema.checkContractVersion('2026-06-28');
```

All runtime entries use Client Key authentication for public schema discovery, with an optional user JWT. No runtime entry exports `createServiceClient` or `ServiceTidbClient`.

## Data API Builders

Data builders are immutable. Each chained method returns a new builder without mutating previous builder state.

### `select(columns?)`

Creates a `GET` request for a published table.

```ts
tidb.from('tasks').select('id,title,status');
tidb.from('tasks').select(); // equivalent to all published columns
```

Unsupported select grammar:

- embedded relationship grammar such as `comments(*)`
- aggregate grammar such as `status.count()`
- raw PostgREST expression compatibility

### Filters

```ts
tidb.from('tasks').select('id,title').eq('id', 1);
tidb.from('tasks').select().neq('status', 'done');
tidb.from('tasks').select().gt('id', 10);
tidb.from('tasks').select().gte('created_at', '2026-01-01');
tidb.from('tasks').select().lt('id', 100);
tidb.from('tasks').select().lte('id', 100);
tidb.from('tasks').select().like('title', '%ship%');
tidb.from('tasks').select().ilike('title', '%ship%');
tidb.from('tasks').select().is('deleted_at', null);
tidb.from('tasks').select().in('status', ['todo', 'doing']);
```

Filter values are encoded into stable Data API query parameters and sent verbatim (the backend binds them as parameters; the SDK does not quote or wrap them). The builder must not expose raw SQL fragments.

`in()` list values are comma-joined into the `in.(...)` grammar. A value that itself contains a comma cannot be represented in that grammar and is rejected locally with an `unsupported` error (`unsupported_filter_value`); use one `eq()` per value instead.

### Ordering And Pagination

```ts
tidb.from('tasks').select('id,title').order('created_at', { ascending: false });
tidb.from('tasks').select().limit(20);
tidb.from('tasks').select().offset(40);
tidb.from('tasks').select().range(0, 19);
tidb.from('tasks').select().count('exact');
```

`range()` maps to the `Range` header.

### `insert(values)`

Creates a `POST` request.

```ts
tidb.from('tasks').insert({ title: 'Ship SDK', status: 'todo' });
tidb.from('tasks').insert([
  { title: 'Write docs', status: 'todo' },
  { title: 'Run gates', status: 'doing' }
]);
```

### `upsert(values, { onConflict })`

Creates a merge-duplicates `POST` request with an explicit conflict column.

```ts
tidb
  .from('tasks')
  .upsert({ id: 1, title: 'Ship SDK', status: 'doing' }, { onConflict: 'id' });
```

The builder may emit `Prefer: resolution=merge-duplicates`. It must not emit PostgreSQL `RETURNING` preferences.

### `update(values)`

Creates a `PATCH` request.

```ts
tidb.from('tasks').update({ status: 'done' }).eq('id', 1);
```

`PATCH` requires at least one filter. Unfiltered updates are rejected locally before fetch.

### `delete()`

Creates a `DELETE` request.

```ts
tidb.from('tasks').delete().eq('id', 1);
```

`DELETE` requires at least one filter. Unfiltered deletes are rejected locally before fetch.

### `toRequest()`

Returns a serializable request descriptor without executing the request.

```ts
const request = tidb.from('tasks').select('id,title').limit(2).toRequest();
```

Example descriptor:

```ts
{
  method: 'GET',
  path: '/table/tasks?select=id,title&limit=2'
}
```

### `execute()`

Executes the request through the SDK request pipeline.

```ts
const result = await tidb.from('tasks').select('id').execute();
```

Response shape:

```ts
{
  data: unknown | null,
  error: DataApiError | TidbCloudSDKError | null,
  status: number,
  statusText: string,
  headers: Headers,
  requestId: string,
  count?: number | null,
  meta?: unknown
}
```

`count` is populated from the response envelope's `meta.count` (for example when `count('exact')` is requested and granted), and is `null` when the backend returns no count. `meta` carries the raw response `meta` envelope (fields such as `rows_affected`, `limit`, `offset`) when present. Both are omitted on error and network-failure results.

Builders are promise-like, so `await tidb.from('tasks').select('id')` is equivalent to `.execute()`.

When `options.data.schema` is provided, builders reject actions or columns not listed in published resource metadata before sending a request. This local guard is only an early error; the service remains the final authorization boundary.

### `throwOnError()`

Throws normalized SDK errors instead of returning them in the result envelope.

```ts
const rows = await tidb
  .from('tasks')
  .select('id')
  .throwOnError();
```

## Auth

`client.auth` exposes application Auth APIs with Supabase-like ergonomics.

These methods follow a never-throw contract: they resolve to a `{ data, error }` envelope rather than rejecting. Malformed responses, network failures, and throwing storage adapters or listeners are all surfaced as `error`, not exceptions. (The Data API builder's `throwOnError()` is the one explicit opt-out.)

### `signInWithPassword(credentials)`

```ts
const { data, error } = await tidb.auth.signInWithPassword({
  email: 'ada@example.test',
  password: 'correct-password'
});
```

On success, the SDK stores the normalized session, emits `SIGNED_IN`, and uses the access token for subsequent Data API calls unless `options.auth.getAccessToken` overrides the token source.

### `signInWithOAuth(credentials)`

Starts a GitHub or Google OAuth flow.

```ts
const { data, error } = await tidb.auth.signInWithOAuth({
  provider: 'github',
  options: {
    redirectTo: 'https://app.example.test/auth/callback',
    queryParams: {
      prompt: 'consent'
    }
  }
});
```

Supported providers:

- `github`
- `google`

Options:

- `redirectTo`: application callback URL registered in the project Auth provider configuration.
- `queryParams`: provider-specific authorization parameters.
- `skipBrowserRedirect`: when `true`, returns the authorization URL without calling `window.location.assign`.
- `nonce`: optional nonce forwarded to the Auth service.

Behavior:

- The SDK calls `/auth/v1/authorize/{provider}` with `apikey`, `redirect_to`, `queryParams`, PKCE `code_challenge`, and `code_challenge_method=S256`.
- Provider OAuth scopes are configured by the Auth service and cannot be selected by public clients.
- `skipBrowserRedirect: true` also sends `skip_http_redirect=true`.
- The Auth service remains responsible for provider enablement, redirect allowlist checks, state creation, and secret handling.
- The SDK stores the PKCE code verifier under the Supabase-compatible key `${storageKey}-code-verifier`.
- In browser runtimes, the SDK redirects to the returned authorization URL unless `skipBrowserRedirect` is true.

Return shape:

```ts
{
  data: {
    provider: 'github',
    url: 'https://api.example.test/auth/v1/callback/github?...'
  },
  error: null
}
```

### `exchangeCodeForSession(code, options?)`

Completes a GitHub or Google OAuth callback by exchanging the provider code for an application session through the TiDB Cloud Auth callback contract.

```ts
const url = new URL(window.location.href);
const { data, error } = await tidb.auth.exchangeCodeForSession(
  url.searchParams.get('code') ?? '',
  {
    provider: 'github',
    state: url.searchParams.get('state') ?? undefined,
    redirectTo: 'https://app.example.test/auth/callback'
  }
);
```

If the saved PKCE state exists, `provider`, `state`, `redirectTo`, and `codeVerifier` can be loaded from storage. The SDK calls `/auth/v1/callback/{provider}`, stores the returned session, emits `SIGNED_IN`, and clears `${storageKey}-code-verifier`.

The session type includes optional `provider_token` and `provider_refresh_token` fields when the Auth service returns provider tokens.

### `exchangeOAuthCallbackForSession(url, options?)`

Exchanges a callback URL in browser, server, or edge handlers without reading service keys or browser-only globals.

```ts
await tidb.auth.exchangeOAuthCallbackForSession(request.url, {
  provider: 'github',
  redirectTo: 'https://app.example.test/auth/callback'
});
```

The response includes the normalized session and a `cleanup` description for removing OAuth credentials from URLs.

### `resetPasswordForEmail(email, options?)`

Calls `/auth/v1/recover`.

```ts
await tidb.auth.resetPasswordForEmail('ada@example.test', {
  redirectTo: 'https://app.example.test/reset-password'
});
```

### `updateUserPassword(credentials)`

Calls `/auth/v1/update-password`.

```ts
await tidb.auth.updateUserPassword({
  token: recoveryToken,
  password: 'new-password'
});
```

### `signOut(options?)`

Revokes the current session on the server and clears local session state. `scope` is a narrow SDK type (`current`, `all`, or `global`).

```ts
await tidb.auth.signOut({ scope: 'current' });
```

Local-clear behavior when the server logout call fails:

- **`4xx` (e.g. the token was already revoked):** the server has answered, so the local session is cleared and `SIGNED_OUT` is emitted; the error is still returned in the envelope.
- **`5xx` or a network failure:** the logout may not have taken effect server-side, so the local session is **kept** and the error is returned. The caller can retry without losing the credential handle.

### `initialize()`

Initializes Auth callback recovery for browser runtimes. By default, the client calls this during construction when `options.auth.detectSessionInUrl !== false`.

```ts
await tidb.auth.initialize();
```

When the current URL contains OAuth `code` and `state`, `initialize()` exchanges the code, stores the session, emits `SIGNED_IN`, clears `${storageKey}-code-verifier`, and removes OAuth credentials from the browser URL. Disable automatic callback recovery with:

```ts
const tidb = createClient(url, clientKey, {
  auth: {
    detectSessionInUrl: false
  }
});
```

### Session Lifecycle

#### `getSession()` / `getUser(accessToken?)` / `refreshSession(session?)`

```ts
const { data: { session } } = await tidb.auth.getSession();
const { data: { user } } = await tidb.auth.getUser();
await tidb.auth.refreshSession();
```

`getSession()` returns the current stored session and refreshes it inline when it is inside the refresh window. `refreshSession()` calls `/auth/v1/token?grant_type=refresh_token`. Concurrent refreshes are de-duplicated in-process, and across tabs the refresh is serialized with the Web Locks API (`navigator.locks`) when available, so a single-use refresh token is not consumed twice.

When the service returns the exact error code `session_database_changed`, the saved session belongs to the project's old database. The shared client clears that session, stops automatic refresh, sends `SIGNED_OUT` to listeners and other tabs, and returns the original safe error so the app can ask the user to sign in again. This applies to Auth, Data API, and schema requests made through `createClient()`. Network errors, server errors, normal `403` errors, and other error codes do not clear the session.

If an app sends requests outside the shared client, it can pass the returned error to `tidb.auth.handleSessionError(error)`. The method returns `true` only for `session_database_changed`.

#### `onAuthStateChange(callback)`

```ts
const { data: { subscription } } = tidb.auth.onAuthStateChange((event, session) => {
  // event: 'INITIAL_SESSION' | 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED' | 'USER_UPDATED'
});
subscription.unsubscribe();
```

The callback fires once with `INITIAL_SESSION` on subscribe, then on every subsequent state change. Each subscriber callback is isolated — a throwing callback cannot break other subscribers or reject the auth method that triggered the change.

#### Proactive token refresh

When `auth.autoRefreshToken` is `true` (the default), a background timer (30s tick) and a `visibilitychange` listener refresh the session before it expires, so requests do not fail on a stale token after an idle tab or a sleeping laptop. Both are torn down by `close()`. Timers are only started in runtimes that provide `setInterval`; server and edge runtimes without one fall back to the inline refresh in `getSession()`.

#### Cross-tab session sync

With `auth.broadcast` and `auth.storageEvent` enabled (both default), a sign-in, sign-out, or token refresh in one tab is reflected in others. Identical sessions are de-duplicated so a single change is not delivered twice, and a token rotation is labelled `TOKEN_REFRESHED` (not `SIGNED_IN`). Release resources with `tidb.auth.close()`.

### `cleanupOAuthCallbackUrl(input)`

Removes OAuth callback credentials from query strings and hash fragments after the application has handled the callback.

```ts
const cleaned = cleanupOAuthCallbackUrl(window.location.href);
window.history.replaceState(null, '', cleaned.url);
```

## Type Model

Applications can provide a generated or handwritten `Database` type.

```ts
interface Database {
  readonly Tables: {
    readonly tasks: {
      readonly Row: {
        readonly id: number;
        readonly title: string;
      };
      readonly Insert: {
        readonly title: string;
      };
      readonly Update: {
        readonly title?: string;
      };
    };
  };
}
```

Generated types may also include `PublishedResources` metadata for isolation mode, allowed actions, and allowed columns. These types help application code inspect published policy metadata, but they do not replace service-side authorization.

Type helpers:

- `GenericDatabase`
- `GenericTable`
- `GenericRow`
- `TablesOf<Database>`
- `TableName<Database>`
- `TableDefinition<Database, Table>`
- `RowOf<Database, Table>`
- `InsertOf<Database, Table>`
- `UpdateOf<Database, Table>`
- `ColumnName<Database, Table>`

## Auth-Aware Requests

Authenticated Data API requests require both a client key and a user JWT:

```http
apikey: <client-key>
Authorization: Bearer <user-jwt>
```

The SDK obtains the JWT from `options.auth.getAccessToken`.

Bearer-only user JWT requests are invalid by service contract and must fail with a stable authentication error.

## Errors

SDK errors are normalized and JSON-serializable.

Primary error classes:

- `TidbCloudSDKError`
- `DataApiError`
- `AuthApiError`

Error kinds include:

- `network`
- `data_api`
- `auth_api`
- `unsupported` — includes locally-rejected requests such as `unsupported_filter_value` (an `in()` value containing a comma) and mutation guards (`mutation_filter_required`).
- `timeout` — a request exceeded `global.timeoutMs`.
- `unknown`

Error JSON must redact secret-bearing values in messages, details, raw bodies, headers, URLs, and nested payloads.

## Unsupported Public API

The following helpers must not appear in public application entry points:

- `rpc`
- `sql`
- `query`
- `ddl`
- `grant`
- `revoke`
- `admin`
- `createServiceClient` or `ServiceTidbClient` from any public entry point
- `dangerouslyUseServiceKey`

If a future non-public API introduces a scoped Service Key SDK, it must use a separate documented surface and must not forward that credential to Public Data or Schema routes.
