# `@tidbcloud/auth-js`

`@tidbcloud/auth-js` is the TiDB-native Supabase-like application integration SDK for TiDB Cloud Auth and published Data APIs.

It is built for developers who want to integrate browser, server, and edge applications with a Supabase-like developer experience: typed client creation, Data API query builders, Auth-aware request execution, stable error handling, runtime-specific entry points, and generated application types from published metadata.

It is intentionally not a `supabase-js`, PostgREST, PostgreSQL, or PostgreSQL RLS compatibility layer. The SDK mirrors useful application ergonomics, not PostgreSQL behavior.

## Positioning

- Package name: `@tidbcloud/auth-js`.
- Primary users: application developers integrating frontend, server-rendered, or edge applications with TiDB Cloud Supabase-like services.
- Product model: TiDB-native published Data APIs plus Auth/session integration.
- Compatibility model: Supabase-like ergonomics, not `supabase-js` API compatibility.
- Source of truth: service contract fixtures under `tidbcloud-auth/internal/tidbrest/testdata/sdk_requests/` and published service metadata such as `/schema` or `/doc`.

## Install

```sh
npm install @tidbcloud/auth-js
```

Do not publish or consume this package as production-ready until every release gate in `docs/release-checklist.md` passes for the release candidate.

## Quick Start

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

interface Database {
  readonly Tables: {
    readonly tasks: {
      readonly Row: {
        readonly id: number;
        readonly title: string;
        readonly status: 'todo' | 'doing' | 'done';
        readonly created_at: string;
      };
      readonly Insert: {
        readonly title: string;
        readonly status: 'todo' | 'doing' | 'done';
      };
      readonly Update: {
        readonly title?: string;
        readonly status?: 'todo' | 'doing' | 'done';
      };
    };
  };
}

const tidb = createClient<Database>('https://api.example.test', 'public-client-key', {
  auth: {
    getAccessToken: async () => 'user-jwt'
  }
});

const { data, error } = await tidb
  .from('tasks')
  .select('id,title,status')
  .eq('status', 'todo')
  .limit(10);

if (error) {
  console.error(error.toJSON());
}
```

Authenticated Data API requests must send both:

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

Bearer-only requests are rejected by service contract.

## Auth OAuth

The Auth client supports Supabase-like GitHub and Google OAuth entry points.

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

In browsers, `signInWithOAuth()` redirects to the returned authorization URL by default. Use `skipBrowserRedirect: true` to receive `data.url` and handle navigation yourself.

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

OAuth uses PKCE and stores the code verifier under the Supabase-compatible key `${storageKey}-code-verifier`. Provider tokens are available as `session.provider_token` and `session.provider_refresh_token` when the Auth service returns them.

Browser clients automatically attempt OAuth callback recovery during construction. Set `auth.detectSessionInUrl: false` and call `tidb.auth.initialize()` manually when an application framework needs explicit control over callback timing.

Server and edge handlers can exchange a callback URL without relying on browser globals:

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

Password recovery uses the public Auth API and does not expose service-key behavior:

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

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

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

### Auth scope

Auth requests do not send client-chosen scope values. The Auth service resolves the project from the public client key (`apikey`) and the request host or configured custom domain.

The SDK still isolates browser sessions locally. By default the storage key is based on the Auth base URL plus a fingerprint of the public client key, so two apps on the same browser do not share sessions by accident. If you need a fixed local key, pass `auth.storageKey`.

### OAuth 整页跳转与 SSR / Edge 的 storage 要求

OAuth 的「整页跳转」登录（`signInWithOAuth` 不带 `skipBrowserRedirect`）依赖**持久化 storage**。PKCE 的 `code_verifier` 只有在 `persistSession` 为 true 且存在可用 storage 适配器时才会被持久化（见 `#saveOAuthState`）。整页跳转回来后，SDK 是一个全新的实例，只能从 storage 里把 `code_verifier` 读回来。

按运行环境分两种情况：

- **浏览器**：默认就有 `localStorage`，开箱即用。但如果你设了 `persistSession: false`、或浏览器禁用了 `localStorage`，就**不要**依赖整页跳转——要么传入 `global.storage` 自定义 storage，要么用 `skipBrowserRedirect: true` 自己驱动跳转和回调交换。
- **SSR / Edge / 服务端**：这些运行时通常没有 `localStorage`。推荐用「同实例 / 显式」模式，在服务端拿到回调后直接传入你自己捕获的 `codeVerifier`，**不需要**持久化 storage：

```ts
// 服务端回调处理:把你在发起时保存的 codeVerifier 显式传进来
const result = await tidb.auth.exchangeOAuthCallbackForSession(callbackUrl, {
  codeVerifier // 你自己保存/传递的 PKCE verifier
});
// 或更底层:
// await tidb.auth.exchangeCodeForSession(code, { codeVerifier, state, redirectTo });
```

> 这正是 `edgeSecurityPolicy.persistentStorage: 'caller-provided'` 所表达的约定:Edge 入口不自带持久化 storage,由调用方提供。
>
> 说明:在非浏览器运行时,`signInWithOAuth` 本来就**不会**触发整页跳转(`window` 不存在),它只返回授权 URL 交给你处理,内存里仍保有本次的 OAuth state,所以同实例内的后续交换不依赖 storage。

## Public Runtime Entries

```ts
import { createClient } from '@tidbcloud/auth-js';
import { browserSecurityPolicy } from '@tidbcloud/auth-js/browser';
import { serverSecurityPolicy } from '@tidbcloud/auth-js/server';
import { edgeSecurityPolicy } from '@tidbcloud/auth-js/edge';
import { schemaModuleStatus } from '@tidbcloud/auth-js/schema';
import { typegenModuleStatus } from '@tidbcloud/auth-js/typegen';
```

Runtime boundaries are part of the security model:

- Browser entry points accept public client keys only.
- Server entry points do not provide a Service Key client for public Data or Schema APIs; server applications use `createClient()` with a public Client Key and an optional user JWT too.
- Edge entry points must avoid Node-only assumptions and caller-global persistent storage.

## Data API

`createClient()` returns a `TidbClient`.

```ts
const tidb = createClient<Database>('https://api.example.test', 'public-client-key');

await tidb.from('tasks').select('id,title').eq('id', 1);
await tidb.from('tasks').insert({ title: 'Ship', status: 'todo' });
await tidb.from('tasks').update({ status: 'done' }).eq('id', 1);
await tidb.from('tasks').delete().eq('id', 1);
await tidb.from('tasks').upsert({ id: 1, title: 'Ship', status: 'doing' }, { onConflict: 'id' });
```

Supported public Data API routes are limited to published table resources under the canonical `/table/<table>` path style (the default). The legacy `/rest/v1/<table>` path style remains an opt-in (`data.pathStyle: 'rest-v1'`) for type compatibility only — the backend has retired it and now returns HTTP 501.

Mutation builders require an explicit filter for `PATCH` and `DELETE`; broad update/delete operations are rejected locally before a network request.

When published schema metadata is supplied, the builder can also reject disallowed actions or columns before fetch:

```ts
const tidb = createClient<Database>(url, clientKey, {
  data: {
    schema: publishedSchema
  }
});
```

This is only a developer-experience guard. The service remains the final policy, row-filter, and audit boundary.

Read results carry response metadata alongside `data`: `count` (from the response envelope's `meta.count`, e.g. when `count('exact')` is granted) and `meta` (raw envelope fields such as `rows_affected`, `limit`, `offset`):

```ts
const { data, count, meta, error } = await tidb.from('tasks').select('id,title').count('exact');
```

Note: `in()` values sent verbatim into the `in.(...)` grammar cannot contain a comma; such a value is rejected locally with `unsupported_filter_value` — use one `eq()` per value instead.

## Client Options

The request pipeline and session lifecycle are configurable through `createClient(url, key, options)`:

```ts
const tidb = createClient<Database>(url, clientKey, {
  global: {
    timeoutMs: 10_000,      // per-request timeout -> `timeout` error
    retry: { retries: 3 },  // or `false` to disable; only idempotent GET/HEAD/OPTIONS retried
    headers: { 'X-App-Version': 'web-2026' }, // applied to every request (credentials stripped)
    logger, hooks           // redacted request/response logging + lifecycle hooks
  },
  auth: {
    autoRefreshToken: true, // proactive refresh before expiry (default)
    refreshWindowSeconds: 60
  }
});
```

Auth methods never throw — they resolve to `{ data, error }`, surfacing malformed responses, network failures, and throwing storage/listeners as `error`. When `autoRefreshToken` is on, a background timer and `visibilitychange` listener refresh the session before it expires; across tabs, sign-in/out/refresh are synced (de-duplicated, with token rotations labelled `TOKEN_REFRESHED`) and refreshes are serialized via `navigator.locks`. Call `tidb.auth.close()` to release timers and listeners. See [`docs/api-reference.md`](docs/api-reference.md#session-lifecycle) for details.

If the project is moved to a different database, the service returns the exact
code `session_database_changed` for sessions created against the old database.
The shared client removes that saved session, stops refreshing it, and emits
`SIGNED_OUT`; the application should ask the user to sign in again. Network
errors, server failures, and ordinary policy `403` responses do not clear a
valid session.

## Schema Discovery And Typegen

`createClient().schema` exposes published metadata from `/schema`, `/schema/{table}`, and `/doc`:

```ts
const schema = await tidb.schema.getSchema();
const tasks = await tidb.schema.getTableSchema('tasks');
const capabilities = await tidb.schema.getCapabilities();
const contract = await tidb.schema.checkContractVersion('2026-06-28');
```

Schema discovery uses the public Client Key and may add the current user JWT. `X-Service-Key` is not a supported Public Data/Schema credential, and the SDK does not expose `createServiceClient` or `ServiceTidbClient`.

Generated database types keep `Tables.Row/Insert/Update` and add `PublishedResources` metadata for isolation mode, allowed actions, and allowed columns.

## Forbidden Capabilities

The SDK must not generate, expose, document as supported, or silently tunnel these capabilities:

- raw SQL or arbitrary query execution
- DDL, DCL, schema mutation, grant, revoke, or SQL console APIs
- RPC, procedures, routines, or function execution
- admin, system, MCP, UI, dashboard, or internal control-plane routes
- PostgreSQL-specific `RETURNING`, transaction scripting, or PostgREST embedded relationship grammar
- direct TiDB introspection from browser/server application SDK code

Unsupported PostgreSQL/PostgREST-specific behavior must fail with stable errors, not partial emulation.

## Documentation

- API reference: `docs/api-reference.md`
- Migration from `supabase-js`: `docs/migration-supabase-js.md`
- Troubleshooting: `docs/troubleshooting.md`
- Security model: `docs/security.md`
- Release readiness checklist: `docs/release-checklist.md`

## Release Status

This package is production-targeted but release readiness is evidence-based. A release candidate is valid only after:

- contract fixture sync passes against `tidbcloud-auth`
- SDK typecheck, build, unit, contract, type, security, export, bundle, and dry-pack checks pass
- public exports match the documented API
- browser bundles do not contain service-key or server-only capability paths
- docs, changelog, provenance, and tarball allowlist gates pass

See `docs/release-checklist.md` for the authoritative release gate sequence.
