npm package: @seaverse/data-sdk
Description: SeaVerse Data SDK - PostgreSQL/AlloyDB data operations with RLS-based permissions
Author: SeaVerse Team
Latest version: 0.3.3
Deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.

# @seaverse/data-sdk

[![npm version](https://img.shields.io/npm/v/@seaverse/data-sdk.svg)](https://www.npmjs.com/package/@seaverse/data-sdk)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

高性能数据服务 SDK，基于 PostgreSQL/AlloyDB + PostgREST + RLS 架构。

## 🚀 核心概念

### App ID 是什么？

**App ID** 是您在 SeaVerse 平台上创建的应用的唯一标识符。它用于：
- ✅ 多租户数据隔离（每个应用的数据互不干扰）
- ✅ 权限控制（RLS 策略基于 app_id）
- ✅ 资源配额管理

**如何获取 App ID？**
1. 登录 [SeaVerse 控制台](https://console.seaverse.ai)
2. 进入"应用管理"
3. 复制您的 App ID

> ⚠️ **重要**：所有 SDK 操作都需要提供有效的 `appId`，否则无法访问数据。

## 特性

- ✅ TypeScript 类型支持
- ✅ 自动处理 JWT 认证
- ✅ RLS 安全控制
- ✅ 应用隔离
- ✅ 简单易用的 API
- ✅ 完整的错误处理
- ✅ 支持过滤、排序、分页
- ✅ 支持标签和 JSONB 字段查询
- ✅ **零配置使用** - 自动从 PostMessage 获取配置
- ✅ **自动配额限制** - 数据库层强制执行配额，防止资源滥用

## 安装

```bash
npm install @seaverse/data-sdk
```

## 快速开始

### 方式1: 函数式 API（推荐 ⭐）

最简单的使用方式 - 零配置，直接调用函数：

```typescript
import { query, create, update, deleteData, getUserId, getAppId, getUserName } from '@seaverse/data-sdk';

// 第一次调用自动初始化 SDK（从 PostMessage 获取 appId 和 token）
// 无需任何配置代码！

// 获取用户信息
const userId = await getUserId();
const appId = await getAppId();
const userName = await getUserName();
console.log(`用户: ${userName} (${userId}), 应用: ${appId}`);

// 查询数据
const notes = await query({
  table_name: 'notes',
  filters: { category: 'work' },
  order: { field: 'created_at', direction: 'desc' }
});

// 创建数据
const newNote = await create({
  table_name: 'notes',
  data_value: { title: 'My Note', content: '...' },
  visibility: 'private'
});

// 更新数据
await update(newNote.id, {
  data_value: { title: 'Updated Title' }
});

// 删除数据
await deleteData(newNote.id);
```

**为什么推荐函数式 API：**
- ✅ **零配置** - 无需创建客户端实例，直接调用函数
- ✅ **自动初始化** - 第一次调用自动获取配置（PostMessage）
- ✅ **代码简洁** - 代码更简洁，更易理解
- ✅ **单例管理** - SDK 自动管理全局实例，避免重复初始化

---

### 方式2: DataClient 类 API（高级场景）

适用于需要多个实例或自定义配置的场景：

```typescript
import { DataClient } from '@seaverse/data-sdk';

// 方式2a: 零配置（推荐）- 自动从 PostMessage 获取 appId 和 token
const client = await DataClient.create();

// 方式2b: 只提供 appId，token 自动获取
const client = await DataClient.create({
  appId: 'my-app-123',
});

// 方式2c: 显式提供所有配置
const client = new DataClient({
  appId: 'my-app-123',
  token: 'user-jwt-token',
});

// 使用 client 实例
const notes = await client.query({
  table_name: 'notes',
  filters: { category: 'work' },
});
```

**配置选项说明：**

| 参数 | 类型 | 必需 | 说明 |
|-----|------|------|------|
| `appId` | string | ⬜️ | 应用ID，未提供则通过PostMessage自动获取 |
| `token` | string | ⬜️ | 用户JWT token，未提供则通过PostMessage自动获取 |
| `baseURL` | string | ⬜️ | 自定义API地址，优先级最高 |
| `environment` | Environment | ⬜️ | 环境类型，当未提供baseURL时使用 |
| `timeout` | number | ⬜️ | 请求超时时间（毫秒），默认10000 |
| `headers` | object | ⬜️ | 自定义请求头 |
| `debug` | boolean | ⬜️ | 是否开启调试日志（默认false） |

> 💡 **初始化提示**：SDK 初始化成功后会在控制台打印确认信息，包括应用 ID、API 地址和 token（脱敏显示）。例如：
> ```
> [DataSDK] ✓ Initialized | App: my-app-123 | API: https://data.seaverse.ai | Token: eyJhbGci...a2fg
> ```
> 如果启用 `debug: true`，还会打印完整的 token 和详细的配置信息（⚠️ 注意：生产环境建议关闭 debug 模式）。

### 配置自动获取

SDK 支持零配置使用，会自动从以下来源获取配置：

- **Token & AppId**: 通过 PostMessage 从父窗口获取，或显式传入
- **BaseURL**: 自动检测运行环境，或通过 `environment` 参数指定
- **环境变量**: Node.js 环境支持 `DATA_SDK_BASE_URL` 覆盖默认配置

**支持的环境类型：**
```typescript
environment?: 'production' | 'staging' | 'development' | 'local'
```

> 💡 **iframe 集成提示**：在 iframe 中使用时，SDK 会通过 PostMessage 与父页面通信获取配置（token、appId、环境标识），无需手动传递。

## API 文档

### 函数式 API（推荐）

支持零配置，直接调用的便捷函数：

#### 数据操作

| 方法 | 说明 | 参数 | 返回值 |
|------|------|------|--------|
| `query(options)` | 查询数据列表 | `QueryOptions` | `Promise<DataRecord[]>` |
| `get(id)` | 获取单条数据 | `id: string` | `Promise<DataRecord \| null>` |
| `create(options)` | 创建数据 | `CreateDataOptions` | `Promise<DataRecord>` |
| `update(id, options)` | 更新数据 | `id: string`, `UpdateDataOptions` | `Promise<DataRecord>` |
| `deleteData(id)` | 删除数据 | `id: string` | `Promise<void>` |
| `batchDelete(ids)` | 批量删除 | `ids: string[]` | `Promise<void>` |
| `queryWithPagination(options)` | 查询带分页信息 | `QueryOptions` | `Promise<PaginatedResponse<DataRecord>>` |

#### 用户信息

| 方法 | 说明 | 参数 | 返回值 |
|------|------|------|--------|
| `getUserId()` | 获取当前用户 ID | - | `Promise<string>` |
| `getAppId()` | 获取当前应用 ID | - | `Promise<string>` |
| `getUserName()` | 获取当前用户名 | - | `Promise<string \| undefined>` |

#### 单例管理

| 方法 | 说明 | 参数 | 返回值 |
|------|------|------|--------|
| `reinit(options?)` | 重新初始化全局客户端 | `DataClientOptions?` | `Promise<void>` |
| `getClientStatus()` | 获取客户端状态 | - | `{ initialized: boolean, initializing: boolean, hasInstance: boolean }` |

**使用示例：**

```typescript
import { getUserId, getAppId, getUserName, reinit, getClientStatus } from '@seaverse/data-sdk';

// 获取用户信息（首次调用自动初始化）
const userId = await getUserId();        // "1CWwryoYa6MyNxhhAdX3eL"
const appId = await getAppId();          // "app-1768292869555..."
const userName = await getUserName();    // "troye ye"

// 检查客户端状态
const status = getClientStatus();
console.log('已初始化:', status.initialized);

// 重新初始化（例如：用户登录后切换 token）
await reinit({ token: newToken });
```

### DataClient 类方法

使用 `DataClient.create()` 或 `new DataClient(options)` 创建实例后可用的方法：

| 方法 | 说明 | 参数 | 返回值 |
|------|------|------|--------|
| `query(options)` | 查询数据列表 | `QueryOptions` | `Promise<DataRecord[]>` |
| `get(id)` | 获取单条数据 | `id: string` | `Promise<DataRecord \| null>` |
| `create(options)` | 创建数据 | `CreateDataOptions` | `Promise<DataRecord>` |
| `update(id, options)` | 更新数据 | `id: string`, `UpdateDataOptions` | `Promise<DataRecord>` |
| `delete(id)` | 删除数据 | `id: string` | `Promise<void>` |
| `batchDelete(ids)` | 批量删除 | `ids: string[]` | `Promise<void>` |
| `queryWithPagination(options)` | 查询带分页信息 | `QueryOptions` | `Promise<PaginatedResponse<DataRecord>>` |
| `isAdmin()` | 检查管理员权限 | - | `Promise<boolean>` |
| `updateToken(token)` | 更新实例 token | `token: string` | `void` |
| `updateAppId(appId)` | 更新实例 appId | `appId: string` | `void` |
| `getAppId()` | 获取实例 appId | - | `string` |
| `getUserId()` | 获取当前用户 ID | - | `string` |
| `getUserName()` | 获取当前用户名 | - | `string \| undefined` |

### 查询选项（QueryOptions）

```typescript
interface QueryOptions {
  // 表名（必填）
  table_name: string;

  // 过滤条件
  filters?: {
    id?: string;                // 数据 ID
    category?: string;          // 分类
    visibility?: 'public' | 'private'; // 可见性
    tags?: string[];            // 标签
    data_value?: Record<string, any>; // data_value 字段过滤
    created_after?: string;     // 创建时间（之后）
    created_before?: string;    // 创建时间（之前）
  };

  // 排序
  order?: {
    field: 'created_at' | 'updated_at';
    direction: 'asc' | 'desc';
  };

  // 分页
  pagination?: {
    limit: number;
    offset: number;
  };
}
```

### 数据记录（DataRecord）

```typescript
interface DataRecord {
  id: string;                        // 数据唯一标识（UUID）
  app_id: string;                    // 应用ID（多租户隔离）
  table_name: string;                // 逻辑表名（业务数据分类）
  data_value: Record<string, any>;   // 业务数据（JSONB，存储实际内容）
  owner_user_id: string;             // 数据拥有者ID（从JWT自动提取）
  visibility: 'public' | 'private';  // 可见性（public=所有人，private=仅owner+admin）
  category?: string;                 // 业务分类（可选，有索引）
  tags?: string[];                   // 标签数组（可选，多维度分类）
  created_at: string;                // 创建时间（ISO 8601格式）
  updated_at: string;                // 更新时间（ISO 8601格式）
}
```

#### 字段详解

| 字段 | 类型 | 说明 | 自动设置 | 示例 |
|------|------|------|---------|------|
| **id** | `string` | 数据的全局唯一标识符（UUID） | ✅ 自动生成 | `"123e4567-e89b..."` |
| **app_id** | `string` | 应用ID，用于多租户数据隔离 | ✅ SDK自动设置 | `"app-1768292869555..."` |
| **table_name** | `string` | 逻辑表名，用于业务数据分组 | ✅ SDK自动设置 | `"posts"`, `"notes"` |
| **data_value** | `jsonb` | 你的实际业务数据（支持任意JSON结构） | 🔵 必须提供 | `{ "title": "标题" }` |
| **owner_user_id** | `string` | 数据拥有者用户ID | ✅ 从JWT提取 | `"1CWwryoYa6MyNxhh..."` |
| **visibility** | `string` | 数据可见性控制 | 🔵 可指定（默认private） | `"private"` or `"public"` |
| **category** | `string` | 业务分类标签（有专用索引） | ⬜️ 可选 | `"work"`, `"personal"` |
| **tags** | `string[]` | 多维度标签数组 | ⬜️ 可选 | `["react", "tutorial"]` |
| **created_at** | `timestamp` | 数据创建时间 | ✅ 自动生成 | `"2026-01-15T13:31:19.574Z"` |
| **updated_at** | `timestamp` | 数据最后更新时间 | ✅ 自动更新 | `"2026-01-15T13:31:19.574Z"` |

#### 核心字段说明

**data_value - 业务数据**

这是存储你**真正业务数据**的字段，支持任意 JSON 结构：

```typescript
// 创建笔记
const note = await client.create({
  table_name: 'notes',
  data_value: {                    // ← 这个对象会存储到 data_value 字段
    title: '我的笔记',
    content: '笔记内容...',
    author: 'Alice',
    metadata_value: {
      wordCount: 1000,
      tags: ['work']
    }
  }
});

// 访问数据
console.log(note.data_value.title);     // "我的笔记"
console.log(note.data_value.metadata);  // { wordCount: 1000, tags: ['work'] }
```

**owner_user_id - 数据拥有者**

由 SDK 自动从 JWT token 的 `sub` 字段提取，用于权限控制和数据隔离：

```typescript
// JWT token: { "sub": "user-123", ... }

const note = await client.create({
  table_name: 'notes',
  data_value: { title: 'Test' }
  // owner_user_id 自动设置为 "user-123"，无需手动指定
});

// 权限规则：
// - 只有 owner 可以修改/删除自己的数据
// - 其他用户无法访问你的 private 数据
// - admin 可以访问应用内所有数据
```

**visibility - 可见性控制**

控制数据的访问权限，支持两种模式：

- **`private`**（默认）：只有 owner 和 app admin 可以访问
- **`public`**：所有应用内用户都可以读取（但只有 owner 和 admin 可以修改）

```typescript
// 私密数据（默认）
const privateNote = await client.create({
  table_name: 'notes',
  data_value: { title: '私密日记' },
  visibility: 'private'  // 只有你自己和管理员可见
});

// 公开数据
const publicPost = await client.create({
  table_name: 'posts',
  data_value: { title: '技术分享' },
  visibility: 'public'   // 所有人可以读取
});
```

### 获取用户信息

SDK 提供便捷方法从 JWT token 中提取用户信息：

```typescript
import { getUserId, getAppId, getUserName } from '@seaverse/data-sdk';

// 获取用户 ID（从 JWT token 的 sub 字段）
const userId = await getUserId();
console.log('用户 ID:', userId);  // "1CWwryoYa6MyNxhhAdX3eL"

// 获取应用 ID
const appId = await getAppId();
console.log('应用 ID:', appId);  // "app-1768292869555..."

// 获取用户名（从 JWT token 的 payload.username 字段）
const userName = await getUserName();
console.log('用户名:', userName);  // "troye ye"
```

**JWT Token 结构说明：**

SDK 支持以下 JWT token 结构：
```json
{
  "sub": "1CWwryoYa6MyNxhhAdX3eL",        // 用户 ID（标准字段）
  "payload": {
    "user_id": "1CWwryoYa6MyNxhhAdX3eL",  // 用户 ID（备用）
    "username": "troye ye"                 // 用户名
  }
}
```

**`getUserName()` 字段优先级：**
1. `payload.username` - SeaVerse 自定义结构（推荐）
2. `name` - 标准 JWT 字段
3. `username` - 常用字段
4. `preferred_username` - OpenID Connect 标准字段

如果 token 中不包含用户名字段，`getUserName()` 将返回 `undefined`。

## 权限说明

### RLS 自动控制

SDK 会自动通过 RLS 策略控制权限：

- ✅ **应用隔离**：用户只能访问自己应用的数据
- ✅ **用户隔离**：用户只能访问自己的私密数据
- ✅ **公开数据**：所有人可以读取公开数据
- ✅ **管理员权限**：管理员可以访问应用内所有数据

### 操作权限矩阵

| 操作 | Owner | Admin | 其他用户（private） | 其他用户（public） |
|------|-------|-------|-------------------|-------------------|
| 读取 | ✅ | ✅ | ❌ | ✅ |
| 创建 | ✅ | ✅ | ✅（自己的） | ✅（自己的） |
| 修改 | ✅ | ✅ | ❌ | ❌ |
| 删除 | ✅ | ✅ | ❌ | ❌ |

## 错误处理

```typescript
import { SDKError, NotFoundError } from '@seaverse/data-sdk';

try {
  await client.create({ ... });
} catch (error) {
  if (error instanceof SDKError) {
    console.error('错误代码:', error.code);
    console.error('状态码:', error.statusCode);
    console.error('详情:', error.details);
  }
}
```

### 错误代码

- `REQUEST_FAILED` - 请求失败
- `TIMEOUT` - 请求超时
- `NETWORK_ERROR` - 网络错误
- `INVALID_TOKEN` - Token 无效
- `NOT_FOUND` - 数据不存在或无权限
- `PERMISSION_DENIED` - 权限不足
- `VALIDATION_ERROR` - 参数验证失败

### 特殊场景错误处理

**更新操作（update）错误处理：**

```typescript
try {
  const updated = await client.update('note-id-123', {
    data_value: { title: 'Updated Title' },
  });
  console.log('更新成功:', updated);
} catch (error) {
  if (error instanceof NotFoundError) {
    // 数据不存在或无权限修改（最常见的 update 错误）
    console.error('数据不存在或您没有权限修改该数据');
  } else if (error instanceof SDKError) {
    console.error('更新失败:', error.message);
  }
}
```

**常见错误场景：**
- ❌ 数据 ID 不存在
- ❌ 用户无权限修改该数据（非 owner 且非 admin）
- ❌ 数据已被删除
- ❌ 响应格式异常（服务端问题）

**其他操作错误处理：**

```typescript
try {
  // 查询操作
  const notes = await client.query({ table_name: 'notes' });

  // 创建操作
  const newNote = await client.create({
    table_name: 'notes',
    data_value: { title: 'Test' }
  });

  // 分页查询
  const paginatedResult = await client.queryWithPagination({
    table_name: 'posts',
    pagination: { limit: 10, offset: 0 }
  });
} catch (error) {
  if (error instanceof SDKError) {
    if (error.code === 'REQUEST_FAILED') {
      // 服务端返回了空响应或响应格式异常
      console.error('服务请求失败，请稍后重试:', error.message);
    } else {
      console.error('操作失败:', error.message);
    }
  }
}
```

## 高级用法

### PostMessage 协议集成

data-sdk 通过 PostMessage 协议与父页面通信获取配置：

```typescript
import { DataClient } from '@seaverse/data-sdk';

// SDK 会自动通过 PostMessage 从父页面获取 appId 和 token
// 父页面需要响应以下消息：
// - seaverse:get_token → 返回 token
// - seaverse:get_appId → 返回 appId

// 零配置初始化（会自动通过 PostMessage 获取配置）
const client = await DataClient.create();

// 使用 data-sdk
const notes = await client.query({ table_name: 'notes' });
```

**配置获取优先级：**
1. 显式传入的参数（最高优先级）
2. PostMessage 从父窗口获取（使用 `seaverse:get_token` 和 `seaverse:get_appId` 协议）

### 无缝接入场景

#### React 应用中使用

```typescript
import { DataClient } from '@seaverse/data-sdk';
import { useEffect, useState } from 'react';

function App() {
  const [client, setClient] = useState<DataClient | null>(null);

  useEffect(() => {
    // 应用启动时初始化客户端
    // SDK 会自动通过 PostMessage 从父页面获取配置
    DataClient.create({ debug: true }).then(setClient);
  }, []);

  const loadNotes = async () => {
    if (!client) return [];

    const notes = await client.query({
      table_name: 'notes',
      order: { field: 'created_at', direction: 'desc' },
    });
    return notes;
  };

  return <div>...</div>;
}
```

#### iframe 嵌入场景

**父页面（提供配置）：**

```typescript
// 监听来自 iframe 的配置请求
window.addEventListener('message', (event) => {
  // 处理 token 请求
  if (event.data.type === 'seaverse:get_token') {
    // 从你的认证系统获取 token
    const token = getAuthToken(); // 你的 token 获取逻辑

    if (token) {
      event.source.postMessage({
        type: 'seaverse:token',
        payload: {
          accessToken: token,
          expiresIn: 3600
        }
      }, '*');
    } else {
      event.source.postMessage({
        type: 'seaverse:error',
        error: 'Token not found'
      }, '*');
    }
  }

  // 处理 appId 请求
  if (event.data.type === 'seaverse:get_appId') {
    // 从你的配置中获取 appId
    const appId = getAppId(); // 你的 appId 获取逻辑

    if (appId) {
      event.source.postMessage({
        type: 'seaverse:appId',
        payload: {
          appId: appId
        }
      }, '*');
    } else {
      event.source.postMessage({
        type: 'seaverse:error',
        error: 'AppId not found'
      }, '*');
    }
  }
});
```

**iframe 页面（自动获取）：**

```typescript
import { DataClient } from '@seaverse/data-sdk';

// 零配置创建客户端，SDK 会自动通过 PostMessage 从父页面获取 appId 和 token
const client = await DataClient.create();
const notes = await client.query({ table_name: 'notes' });
```

**PostMessage 协议说明：**

| 协议 | 请求 type | 响应 type | 响应 payload |
|------|----------|----------|-------------|
| Token | `seaverse:get_token` | `seaverse:token` | `{ accessToken: string, expiresIn: number }` |
| AppId | `seaverse:get_appId` | `seaverse:appId` | `{ appId: string }` |
| 错误 | - | `seaverse:error` | `{ error: string }` |

#### 服务端渲染（SSR/SSG）

```typescript
import { DataClient } from '@seaverse/data-sdk';

export async function getServerSideProps() {
  // 服务端需要显式提供配置
  const client = new DataClient({
    appId: process.env.APP_ID,
    token: process.env.API_SERVICE_TOKEN,
  });

  const notes = await client.query({
    table_name: 'notes',
    filters: { visibility: 'public' },
  });

  return { props: { notes } };
}
```

### 动态更新 Token

```typescript
// JWT 过期后更新 token
client.updateToken('new-jwt-token');
```

### 切换应用

```typescript
// 切换到其他应用
client.updateAppId('another-app-id');
```

### 配额限制

Data SDK 在数据库层实现了自动配额限制，防止单个应用占用过多资源。

#### 配额说明

SDK 支持以下配额限制（在数据库层通过触发器强制执行，无法绕过）：

| 配额类型 | 默认值 | 说明 |
|---------|-------|------|
| 总数据条数 | 100,000 条 | 单个应用的最大数据总量 |
| 单表数据条数 | 50,000 条 | 单个 table_name 的最大数据量 |
| 存储空间 | 1 GB | 单个应用的最大存储空间 |
| 每分钟操作 | 1,000 次 | 防止短时间内大量操作 |
| 每小时操作 | 10,000 次 | 防止持续高频操作 |
| 每天操作 | 100,000 次 | 防止每日滥用 |

**配额检查机制：**
- 创建操作（CREATE）：检查数据总量、单表数据量、存储空间、操作频率
- 更新操作（UPDATE）：检查存储空间变化、操作频率
- 删除操作（DELETE）：检查操作频率

超限时会自动抛出错误，操作被拒绝并回滚。

#### 错误处理

```typescript
try {
  await client.create({
    table_name: 'notes',
    data_value: { title: 'New Note' },
  });
} catch (error) {
  // 检查是否是配额超限错误
  if (error.message && error.message.includes('Quota exceeded')) {
    console.error('配额超限:', error.message);
    // 示例错误信息：
    // "Quota exceeded: Total records quota exceeded: 100000/100000 records"
    // "Quota exceeded: Storage quota exceeded: 1024.5 MB/1024 MB"
    // "Quota exceeded: Rate limit exceeded: 1234/1000 operations in last minute"
  } else {
    console.error('操作失败:', error);
  }
}
```

#### 平台管理员监控

配额的监控和调整由平台管理员通过数据库查询完成：

```sql
-- 查看所有应用的使用情况
SELECT
  u.app_id,
  u.total_records,
  ROUND(u.total_storage_bytes::numeric / 1024 / 1024, 2) as storage_mb,
  q.max_total_records,
  ROUND((u.total_records::numeric / q.max_total_records * 100), 2) as usage_percent
FROM app_usage_stats u
LEFT JOIN app_quotas q ON u.app_id = q.app_id
ORDER BY usage_percent DESC;

-- 查找接近配额的应用（预警）
SELECT app_id, usage_percent
FROM (
  SELECT
    u.app_id,
    ROUND((u.total_records::numeric / q.max_total_records * 100), 2) as usage_percent
  FROM app_usage_stats u
  INNER JOIN app_quotas q ON u.app_id = q.app_id
) t
WHERE usage_percent > 80;

-- 为应用调整配额
UPDATE app_quotas
SET max_total_records = 500000,
    max_storage_bytes = 5368709120  -- 5GB
WHERE app_id = 'your-app-id';
```

### JSONB 字段查询

```typescript
// 查询 data_value 中的字段
const results = await client.query({
  table_name: 'posts',
  filters: {
    data_value: {
      title: 'Hello World',  // data_value->>'title' = 'Hello World'
      author: 'Alice',        // data_value->>'author' = 'Alice'
    },
  },
});
```

### 标签查询

```typescript
// 查询包含指定标签的数据
const results = await client.query({
  table_name: 'posts',
  filters: {
    tags: ['javascript', 'typescript'], // 包含任一标签
  },
});
```

### 时间范围查询

```typescript
// 查询指定时间范围的数据
const results = await client.query({
  table_name: 'logs',
  filters: {
    created_after: '2026-01-01T00:00:00Z',
    created_before: '2026-01-31T23:59:59Z',
  },
});
```

### 分页查询

```typescript
// 查询带总数的分页数据
const result = await client.queryWithPagination({
  table_name: 'posts',
  pagination: { limit: 10, offset: 0 },
});

console.log(`总共 ${result.total} 条，当前返回 ${result.data.length} 条`);
console.log(`是否还有更多: ${result.hasMore}`);
```

## 性能说明

基于新的 RLS 架构，SDK 具有以下性能特性：

- ⚡ **查询 10000 条数据**：~30ms（vs 旧架构 50 秒）
- ⚡ **STABLE 函数缓存**：管理员查询自动缓存
- ⚡ **短路求值**：优先检查 visibility 和 owner
- ⚡ **哈希分区**：256 个分区，支持并行查询

## 架构说明

### JWT Token 架构

Data SDK 采用标准的 JWT 认证机制：

**Token 结构：**
```json
{
  "sub": "user-id-123",           // 用户ID（JWT标准字段）
  "iat": 1736832000,              // 签发时间
  "exp": 1736918400               // 过期时间
}
```

**重要说明：**
- ✅ Token 中**只包含用户身份信息**（`sub` 或 `payload.user_id`）
- ✅ Token 在用户登录时生成，具有固定的有效期
- ❌ Token 中**不包含** `app_id`（因为 app_id 是动态的，用户可能访问多个应用）
- ✅ `app_id` 通过 SDK 在每次请求时传递

### 多租户隔离架构

**隔离层级：**
1. **应用隔离**：`app_id` - 同一用户在不同应用的数据相互隔离
2. **用户隔离**：`user_id` - 同一应用内不同用户的私密数据相互隔离
3. **可见性控制**：`visibility` - 支持 public/private 数据共享

## 数据库初始化

如果你需要自己部署数据库，请参考：

```bash
# 初始化数据库
psql -h <host> -U postgres -d <database> -f init/init-schema.sql
```

详细说明请查看 `init/README.md`。

## 版本历史

### v0.3.3 (2026-01-29)

**🔧 开发体验改进**

- ✅ **新增详细日志输出**：在 Token 和 AppId 获取流程中添加完整的日志追踪
  - 显示配置来源（显式传入 / PostMessage）
  - 显示 PostMessage 通信过程（发送、等待、响应）
  - 显示超时和错误信息
  - 使用 emoji 标识不同状态（🔍 开始、✅ 成功、❌ 失败、⚠️ 警告等）
- ✅ **新增日志文档**：创建 `LOGS_DOCUMENTATION.md` 详细说明所有日志场景
- ✅ **改进调试体验**：开发者可以清楚看到 SDK 初始化的每一步

**📚 文档更新**

- ✅ 添加完整的日志输出示例
- ✅ 添加父页面 PostMessage 响应示例
- ✅ 添加常见问题和调试建议

### v0.3.2 (2026-01-29)

**✨ 新增功能**

- ✅ **新增 `getUserId()` 函数式 API**：从 JWT token 中获取用户 ID（sub 字段）
- ✅ **新增 `getAppId()` 函数式 API**：获取当前应用 ID
- ✅ **新增 `getUserName()` 函数式 API**：从 JWT token 中获取用户名（支持 payload.username、name、username、preferred_username 字段）
- ✅ **新增 `reinit()` 函数**：重新初始化全局客户端（用于切换 token 或 appId）
- ✅ **新增 `getClientStatus()` 函数**：获取客户端状态（是否已初始化、是否正在初始化）

**🔄 API 变更**

- ❌ **移除 `isAdmin()` 函数式 API**：该方法仍保留在 DataClient 类中，但不再提供函数式 API
- ✅ **DataClient 类新增 `getUserName()` 方法**：与函数式 API 保持一致

**📚 文档更新**

- ✅ **完善 API 文档**：函数式 API 文档按功能分类（数据操作、用户信息、单例管理）
- ✅ **新增"获取用户信息"专题**：详细说明 JWT token 结构和字段提取优先级
- ✅ **更新快速开始示例**：展示用户信息获取的使用方式

**🔧 实现优化**

- ✅ **优化 JWT token 解析**：重构为 `getTokenClaims()` 通用方法，提高代码复用性
- ✅ **兼容多种 JWT 结构**：支持标准 JWT 字段和 SeaVerse 自定义字段（payload.username、payload.user_id）

### v0.2.0 (2026-01-15)

**🔄 重大变更（Breaking Changes）**

- ⚠️ **移除 localStorage 获取**：不再从 `localStorage.auth_token` 和 `localStorage.app_id` 自动获取配置
- ⚠️ **移除环境变量获取**：不再从 `process.env.API_SERVICE_TOKEN` 和 `process.env.APP_ID` 自动获取配置
- ✅ **统一 PostMessage 获取**：appId 和 token 现在只通过 PostMessage 协议从父页面获取
- ✅ **简化配置逻辑**：减少配置来源，统一使用 PostMessage 通信
- ✅ **更适合 iframe 场景**：专注于 iframe 嵌入式应用的使用场景

**迁移指南：**

如果你之前依赖 localStorage 或环境变量：
```typescript
// 旧方式（v0.1.x）- 不再支持
localStorage.setItem('auth_token', token);
localStorage.setItem('app_id', appId);

// 新方式（v0.2.0）- 显式传入或通过 PostMessage
await initData({
  appId: 'your-app-id',
  token: 'your-token',
});

// 或者在父页面响应 PostMessage
window.addEventListener('message', (event) => {
  if (event.data.type === 'seaverse:get_token') {
    event.source.postMessage({
      type: 'seaverse:token',
      payload: { accessToken: yourToken, expiresIn: 3600 }
    }, '*');
  }
});
```

### v0.1.13 (2026-01-15)

**🐛 Bug 修复**

- ✅ **修复 update 方法空响应错误**：修复当服务端返回空响应时，尝试访问 `response.data` 导致 "Cannot read properties of undefined (reading 'data')" 的错误
- ✅ **增强响应验证**：为 `query`, `create`, `update`, `queryWithPagination` 方法添加响应有效性检查
- ✅ **改进错误处理**：当服务端响应异常时，抛出更明确的错误信息

**📚 文档增强**

- ✅ **完善 DataRecord 字段说明**：新增详细的字段说明表格和核心字段解释
- ✅ **补充 data_value 使用示例**：详细说明业务数据的存储和访问方式
- ✅ **增强权限控制说明**：补充 owner_user_id 和 visibility 的详细说明和示例
- ✅ **完善错误处理文档**：新增详细的错误处理示例和常见错误场景说明

### v0.1.12 (2026-01-15)

**📚 文档增强**

- ✅ **完善 API 文档**：详细说明 `getCurrentAppId()` 和 `getAppId()` 的区别
- ✅ **使用场景对比**：添加了完整的使用场景和最佳实践指南
- ✅ **示例代码**：提供了丰富的代码示例和对比表格

### v0.1.11 (2026-01-15)

**🐛 Bug 修复**

- ✅ **导出 getAppId 函数**：修复 `getAppId` 函数未导出的问题
- ✅ **完善 API 导出**：确保所有公开 API 都正确导出

### v0.1.10 (2026-01-15)

**🎯 初始化日志增强**

- ✅ **初始化日志打印**：SDK 初始化时自动在控制台打印配置信息
- ✅ **Token 脱敏显示**：普通模式下 token 脱敏显示（前8位+后4位）
- ✅ **Debug 完整日志**：开启 debug 模式时显示完整 token 和详细配置
- ✅ **清晰可见**：使用 `[DataSDK] ✓ Initialized` 格式，方便调试和问题排查

**示例输出：**
```
[DataSDK] ✓ Initialized | App: my-app-123 | API: https://data.seaverse.ai | Token: eyJhbGci...a2fg
```

### v0.1.9 (2026-01-15)

**🔧 PostMessage 协议调整**

- ✅ **协议命名优化**：PostMessage appId 协议从 `seaverse:get_appid` / `seaverse:appid` 改为 `seaverse:get_appId` / `seaverse:appId`（驼峰命名）
- ✅ **保持一致性**：与 token 协议命名风格保持一致

### v0.1.8 (2026-01-15)

**🎉 AppId 自动获取功能**

- ✅ **AppId 自动获取**：支持从 localStorage/环境变量/PostMessage 自动获取 appId
- ✅ **PostMessage 协议扩展**：新增 `seaverse:get_appId` / `seaverse:appId` 协议
- ✅ **零配置使用**：`initData()` 和 `DataClient.create()` 现在可以不传任何参数
- ✅ **优先级统一**：appId 和 token 采用相同的获取优先级
- ✅ **环境变量支持**：新增 `APP_ID` 环境变量支持

**AppId 获取优先级：**
1. 显式传入的 `appId` 参数
2. `localStorage.app_id`
3. 环境变量 `APP_ID`
4. PostMessage 从父页面获取

### v0.1.7 (2026-01-15)

**🔧 完全采用 seacloud-sdk 的 Token 获取逻辑**

- ✅ **移除 authClient 参数**：简化 API，不再支持直接传入 auth-sdk client
- ✅ **统一 Token 获取方式**：完全采用 seacloud-sdk 的实现
- ✅ **PostMessage 协议统一**：使用 `seaverse:get_token` / `seaverse:token` 协议（与 seacloud-sdk 一致）
- ✅ **环境变量更新**：使用 `API_SERVICE_TOKEN` 而非 `DATA_SDK_TOKEN`
- ✅ **优先级调整**：token → localStorage.auth_token → env → PostMessage

### v0.1.6 (2026-01-14)

**🎉 支持直接调用 auth-sdk API**（已在 v0.1.7 移除）

- ❌ ~~新增 authClient 参数~~（v0.1.7 已移除）
- ✅ 完整示例：新增 `examples/auth-sdk-integration.ts` 演示各种集成场景

### v0.1.5 (2026-01-14)

**🔧 Token 获取优化（参考 seacloud-sdk）**

- ✅ **Token Key 优先级调整**：优先从 `localStorage.auth_token` 读取（与 seacloud-sdk 保持一致）
- ✅ **改进跨平台兼容性**：使用 `globalThis` 确保浏览器和 Node.js 环境都能正常工作
- ✅ **优化错误处理**：localStorage 访问失败时提供友好的错误提示

### v0.1.4 (2026-01-14)

**🔧 API 清理**

- ✅ **移除 `getAppId()` 函数**：appId 必须通过 `initData()` 明确提供，不再有获取函数
- ✅ **简化初始化逻辑**：`getClient()` 要求必须先调用 `initData()`，提供更清晰的错误提示

### v0.1.3 (2026-01-14)

**🔧 配置优化**

- ✅ **Token 获取优化**：优先从 `localStorage.token` 读取（auth-sdk 标准 key）
- ✅ **AppId 要求明确**：移除自动获取 appId 的功能，必须由用户明确提供
- ✅ 简化配置逻辑：减少不必要的自动推断

### v0.1.2 (2026-01-14)

**🎉 无缝接入功能更新**

- ✅ 新增函数式 API：`query()`, `create()`, `update()` 等便捷函数
- ✅ 可选初始化：支持 `initData()` 或零配置直接使用
- ✅ 自动 Token 获取：从 localStorage/PostMessage/环境变量自动获取
- ✅ iframe 支持：通过 PostMessage 协议支持跨窗口通信
- ✅ 全局配置管理：`updateToken()`, `updateAppId()`, `clearConfig()`
- ✅ 完整向后兼容：保留 DataClient 类 API

### v0.1.1 (2026-01-14)

**🔧 配置更新**

- ✅ 简化架构：JWT 只包含 user_id
- ✅ RLS 策略优化：只检查用户权限
- ✅ 性能提升：查询速度提升 1700 倍

### v0.1.0 (2026-01-10)

**🎉 首个版本发布**

- ✅ 基于 PostgreSQL/AlloyDB + PostgREST + RLS 架构
- ✅ 完整的 CRUD 操作支持
- ✅ 多环境配置与自动检测
- ✅ 分页查询支持
- ✅ TypeScript 类型定义

## 相关资源

- [SeaVerse 官网](https://seaverse.ai)
- [开发者文档](https://docs.seaverse.ai)
- [GitHub 仓库](https://github.com/seaverseai/sv-sdk)
- [问题反馈](https://github.com/seaverseai/sv-sdk/issues)

## License

MIT © [SeaVerse Team](https://seaverse.ai)