Data SDK(@iga-pages/data)是 IGA Pages 提供的轻量数据 CRUD SDK。它在应用的 Node.js 服务端运行,通过 IGA Pages 数据服务读写当前项目的数据,提供 create、get、list、update、remove 五个 API。本文说明 SDK 的适用场景、使用前提、数据模型与限制、各 API 的参数和行为,以及错误处理方式。
| 能力 | API | 说明 |
|---|---|---|
| 创建记录 | create | 写入一条业务记录,返回数据服务生成的记录 ID。 |
| 查询单条记录 | get | 按 ID 获取记录;记录不存在时返回 null。 |
| 查询记录列表 | list | 按业务字段过滤并分页查询。 |
| 更新记录 | update | 按 ID 更新指定业务字段。 |
| 删除记录 | remove | 按 ID 删除记录。 |
AI Coding 工具生成的待办、项目、日记、收支等个人工作台类应用,常把数据保存在浏览器 localStorage 中。这类数据只存在于当前浏览器:换一台设备或换一个浏览器就看不到,清理浏览器数据后可能丢失。Data SDK 把这些业务数据保存到数据服务中,让网页 Demo 成为可以长期使用的应用。
ID、CreatedAt 和 UpdatedAt。Data SDK 适合保存个人或小团队应用的轻量业务数据,并按字段精确筛选、分页浏览。例如:
Data SDK 提供单条记录的增删改查和按创建时间排序的分页列表。应用需要以下能力时,请选择其他数据存储方案:
调用 Data SDK 前,确认项目和运行环境满足以下条件:
项目类型:Data SDK 仅支持 Pages 项目。Agent 项目直接依赖 Data SDK 时,CLI 会在创建或关联项目(iga agents link)以及本地启动、构建和部署前报错。
Node.js 版本:Node.js 20 或更高版本。
调用位置:只能在 IGA Pages 服务端运行时中调用,例如:
仅在服务端代码中导入 SDK。浏览器需要读写数据时,由应用的服务端接口代为调用,不要在浏览器代码或 "use client" 模块中导入 SDK。
凭证:SDK 自动使用 IGA Pages 注入的 Page SDK Token(环境变量 IGA_AUTH_KEY),应用代码无需配置服务地址或凭证。缺少 Token 时,调用会抛出 ConfigurationError,详见错误处理。
开通:项目首次调用 Data SDK 时,数据服务自动完成数据存储配置,无需单独开通。
在项目根目录执行:
npm install @iga-pages/data
SDK 同时提供 ESM、CommonJS 和 TypeScript 类型声明。
本地开发时,先登录并关联项目,再启动本地开发服务:
iga login iga pages link iga pages dev
iga pages dev 会为本地服务端运行时注入当前项目的 Page SDK Token。应用代码不需要、也不应该自行读取或传递 Token。
以下代码依次创建、查询、分页列出、更新和删除一条记录。将代码放在服务端模块(如 API Handler)中,并通过 iga pages dev 启动本地开发服务后调用:
import { create, get, list, update, remove, } from "@iga-pages/data"; const { ID } = await create({ title: "编写 PRD", status: "todo", }); const record = await get(ID); const page = await list({ filter: { status: "todo" }, pageNumber: 1, pageSize: 20, }); await update(ID, { status: "done" }); await remove(ID);
每条记录是一个 DataRecord 对象。业务字段可以自由定义,系统字段由数据服务维护:
interface DataRecord { ID: string; CreatedAt?: string; UpdatedAt?: string; [field: string]: unknown; }
| 字段 | 类型 | 说明 |
|---|---|---|
ID | string | 记录 ID,由数据服务在创建时生成,为 24 位十六进制字符串。 |
CreatedAt | string | 创建时间,RFC 3339 格式的 UTC 时间字符串。 |
UpdatedAt | string | 最后更新时间,RFC 3339 格式的 UTC 时间字符串。每次 update 后更新。 |
| 其他字段 | unknown | 业务字段,由应用写入。 |
记录 ID、时间戳和数据归属由数据服务生成和维护。create 和 update 发送请求前,SDK 会从传入对象中移除以下字段:
IDCreatedAtUpdatedAtaccount_idproject_idcreated_atupdated_at写入的数据须满足以下规则,否则数据服务拒绝请求,SDK 抛出 BusinessError:
$ 开头,不包含 .。_id。null、布尔值、字符串、数字、对象或数组。| 限制项 | 上限 |
|---|---|
单次写入的数据大小(create 的 data 或 update 的 patch,JSON 序列化后) | 1 MB |
| 单次写入的数据嵌套层数 | 4 层 |
| 每个项目的记录数 | 10000 条 |
list 的 filter 字段数 | 16 个 |
list 的 filter 大小(JSON 序列化后) | 32 KB |
超出上限时,数据服务拒绝请求,SDK 抛出 BusinessError。项目记录数达到 10000 条后,create 会失败;删除记录后可以继续创建。
五个 API 的签名如下:
create( data: Record<string, unknown>, options?: DataOperationOptions, ): Promise<{ ID: string }>; get<T extends DataRecord = DataRecord>( id: string, options?: DataOperationOptions, ): Promise<T | null>; list<T extends DataRecord = DataRecord>( query?: ListQuery, options?: DataOperationOptions, ): Promise<ListResult<T>>; update( id: string, patch: Record<string, unknown>, options?: DataOperationOptions, ): Promise<void>; remove( id: string, options?: DataOperationOptions, ): Promise<void>;
所有 API 都支持同一个可选参数 options,用于取消请求,详见取消请求:
interface DataOperationOptions { abortSignal?: AbortSignal; }
create:创建记录 const result = await create(data, options?);
参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
data | Record<string, unknown> | 是 | 要写入的业务字段对象。必须是非 null、非数组的对象,并且可以 JSON 序列化。 |
options.abortSignal | AbortSignal | 否 | 用于取消当前请求。 |
返回值
Promise<{ ID: string }>
返回数据服务生成的记录 ID。需要完整记录时,用该 ID 调用 get。
行为说明
data 中的系统字段后再写入,详见系统字段。data 不是对象,或包含循环引用、BigInt 等无法 JSON 序列化的值时,SDK 抛出 BadRequest。BusinessError。InvalidResponse。示例
const { ID } = await create({ kind: "todo", title: "完善 Data SDK 文档", priority: "high", completed: false, });
get:查询单条记录 const record = await get<T>(id, options?);
参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 是 | 记录 ID,必须是非空字符串。 |
options.abortSignal | AbortSignal | 否 | 用于取消当前请求。 |
返回值
Promise<T | null>
null。传入格式无效的 ID 时同样返回 null。行为说明
id 不是非空字符串时,SDK 抛出 BadRequest。T 只提供 TypeScript 类型提示。SDK 只校验响应外层结构,自定义业务字段的运行时校验由应用自行完成。示例
interface TodoRecord extends DataRecord { kind: "todo"; title: string; completed: boolean; } const todo = await get<TodoRecord>(ID); if (!todo) { console.log("记录不存在"); }
list:分页查询记录 const result = await list<T>(query?, options?);
参数
interface ListQuery { filter?: Record<string, unknown>; pageNumber?: number; pageSize?: number; }
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
query.filter | Record<string, unknown> | 否 | {} | 按业务字段过滤。每个字段按值精确匹配,多个字段需同时满足。必须是对象,否则 SDK 抛出 BadRequest。字段数和大小限制见大小与数量限制。 |
query.pageNumber | number | 否 | 1 | 页码,从 1 开始。小于等于 0 时按 1 处理。 |
query.pageSize | number | 否 | 20 | 每页条数,最大 100。大于 100 时按 100 返回;显式传入 0 时按 10 返回。 |
options.abortSignal | AbortSignal | 否 | — | 用于取消当前请求。 |
说明
SDK 调用层使用 filter 组织过滤字段;发送给数据服务时,业务过滤字段会与 PageNumber、PageSize 放在同一层级。因此,filter 中名为 PageNumber 或 PageSize 的字段会被分页参数覆盖,不参与过滤。
返回值
interface ListResult<T extends DataRecord = DataRecord> { Items: T[]; PageNumber: number; PageSize: number; TotalCount: number; }
| 字段 | 说明 |
|---|---|
Items | 当前页的记录。 |
PageNumber | 实际返回的页码。 |
PageSize | 实际使用的每页条数。pageSize 大于 100 时为 100。 |
TotalCount | 满足 filter 的记录总数。 |
行为说明
TotalCount 和 PageSize 计算页数,逐页调用 list。示例
const result = await list<TodoRecord>({ filter: { kind: "todo", completed: false, }, pageNumber: 1, pageSize: 50, }); console.log(result.Items); console.log(result.TotalCount);
update:更新记录 await update(id, patch, options?);
参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 是 | 记录 ID,必须是非空字符串。 |
patch | Record<string, unknown> | 是 | 需要更新的业务字段。必须是非 null、非数组的对象,并且可以 JSON 序列化。 |
options.abortSignal | AbortSignal | 否 | 用于取消当前请求。 |
返回值
Promise<void>
update 不返回更新后的记录。需要最新记录时,在更新后调用 get。
行为说明
update 按顶层字段替换:patch 中出现的字段整体覆盖原值,未出现的字段保持原值,UpdatedAt 随之更新。{ profile: { name: "A", age: 1 } },调用 update(ID, { profile: { name: "B" } }) 后,profile 变为 { name: "B" }。只修改嵌套对象中的部分字段时,先用 get 读取记录,在应用中合并后再写回完整的嵌套对象。patch 中的系统字段后再写入,详见系统字段。id 不是非空字符串、patch 不是对象或无法 JSON 序列化时,SDK 抛出 BadRequest。patch 不满足数据模型与限制时,SDK 抛出 BusinessError。示例
await update(ID, { completed: true, completedAt: new Date().toISOString(), });
更新后读取最新记录:
await update(ID, { status: "done" }); const updated = await get(ID);
remove:删除记录 await remove(id, options?);
参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 是 | 记录 ID,必须是非空字符串。 |
options.abortSignal | AbortSignal | 否 | 用于取消当前请求。 |
返回值
Promise<void>
行为说明
id 不是非空字符串时,SDK 抛出 BadRequest。BusinessError。示例
await remove(ID);
五个 API 都支持通过 AbortSignal 取消请求:
const controller = new AbortController(); const request = list( { pageNumber: 1, pageSize: 100 }, { abortSignal: controller.signal }, ); controller.abort(); await request;
请求取消后,SDK 抛出 DataError,其 code 为 AbortError。
SDK 对参数、运行环境、请求和响应检查中识别到的失败抛出 DataError。使用 instanceof DataError 识别 SDK 错误,通过 error.code 区分原因;其他异常继续抛出:
import { DataError, list } from "@iga-pages/data"; try { await list(); } catch (error) { if (error instanceof DataError) { console.error(error.message); // 查看失败原因 console.error(error.toJSON()); // 提供给平台排查 } else { throw error; } }
错误结构:
interface DataErrorInfo { code: string; status?: number; message: string; }
| 字段 | 说明 |
|---|---|
code | SDK 的错误分类,取值见错误码。 |
status | 实际 HTTP 状态码。业务失败时为 200;没有 HTTP 响应时省略。 |
message | 上游错误原因;有日志 ID 时附带 log_id。 |
使用 toJSON() 可以得到适合日志记录的对象:
// 示例:HTTP 200,但接口返回业务失败。 // 以下为 error.toJSON() 的结果: { "code": "BusinessError", "status": 200, "message": "PagesProjectNotExist (log_id: abc123)" }
DataError.code 是 SDK 的错误分类,不等同于数据服务的业务错误码。程序应判断 code,不解析 message,也不要直接把上游 status 用作应用接口的响应状态。
| code | 常见场景 |
|---|---|
BadRequest | 参数格式不正确、数据无法 JSON 序列化(如循环引用或 BigInt),或 HTTP 400 |
Unauthorized | HTTP 401,未认证 |
Forbidden | HTTP 403,无访问权限 |
NotFound | HTTP 404 |
Conflict | HTTP 409 |
PayloadTooLarge | HTTP 413,请求体过大 |
TooManyRequests | HTTP 429,请求频率受限 |
TimeoutError | HTTP 408/504,或 fetch 抛出 TimeoutError |
AbortError | fetch 抛出 AbortError,请求被取消 |
ConfigurationError | 缺少 IGA Pages 服务端运行时所需的 IGA_AUTH_KEY |
ConnectionError | fetch 连接失败等异常 |
ServiceUnavailable | HTTP 5xx(504 除外) |
BusinessError | HTTP 成功,但响应中的业务 code 非零 |
InvalidResponse | 响应 JSON 解析失败、响应信封或成功数据结构不符合契约 |
UnknownError | 其他未映射的 HTTP 错误状态 |
当前,鉴权失败(如 Page SDK Token 无效)时,数据服务返回 HTTP 400,SDK 抛出 BadRequest。
message 保留上游错误原因,并在有 log_id 时附带日志 ID。SDK 会对 message 中的凭证脱敏,并将长度限制在 2048 个字符以内。
使用 toJSON() 把错误信息记录在服务端日志中,供问题排查使用。向浏览器返回应用自己的错误提示。
出现 TimeoutError、ConnectionError 或 ServiceUnavailable 时,写操作可能已经在数据服务中完成。重试前按操作类型处理:
create:直接重试可能产生重复记录。重试前,用 list 按业务字段查询,确认记录是否已经创建。remove:首次请求已删除记录时,重试会抛出 BusinessError。以下示例是一个 Next.js Route Handler。GET 从请求参数 page 读取页码,返回当前页记录和记录总数;POST 创建记录后读取完整记录并返回:
import { create, get, list, update, remove, } from "@iga-pages/data"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; export async function GET(request: Request) { const { searchParams } = new URL(request.url); const pageNumber = Math.max( 1, Number.parseInt(searchParams.get("page") ?? "", 10) || 1, ); const result = await list({ filter: { kind: "todo" }, pageNumber, pageSize: 100, }); return Response.json({ items: result.Items, page: result.PageNumber, total: result.TotalCount, }); } export async function POST(request: Request) { const input = await request.json(); const { ID } = await create({ kind: "todo", title: input.title, completed: false, }); const record = await get(ID); return Response.json({ record }, { status: 201 }); }
更新和删除记录时,分别调用:
await update(ID, { completed: true }); await remove(ID);