| name | datatable-for-page-api |
| description | Custom Data Table management API for creating and managing user-defined data tables. Supports two groups: 1) Table Schema Management — create tables, rename tables, update table descriptions, define/modify columns and field types, delete tables, list/search tables, get table detail, copy table structure, check if table has data; 2) Table SQL API Management — create new SQL-based table operation APIs, update existing SQL-based table operation APIs. Use this skill whenever the user wants to work with custom data tables, create a new table, manage table columns or fields, or create/update SQL-based data table APIs. Keywords: data table, custom table, table creation, table schema, columns, fields, SQL API, table operation API. 支持中文:自定义数据表、表结构、字段定义、数据表SQL操作API、创建API、更新API. |
| license | MIT |
Data Table for Page API Skill
Overview
This skill provides a Python client script to access the platform data table REST APIs in sandbox.
Use this skill (@datatable-for-page-api) when the user wants to perform any of the following:
- 创建数据表: create a new custom table with a name and optional description
- 查看/搜索数据表: list existing tables, search by name, get table definition detail
- 修改表结构: rename table, change description, update field/column definitions
- 删除数据表: remove a table definition
- 复制表: copy an existing table structure to a new table
- 创建数据表SQL操作API: create a new SQL-based table operation API for page components
- 更新数据表SQL操作API: update an existing SQL-based table operation API
Agent workflow
创建表并定义SQL操作API(推荐标准流程)
- 调用
add-table(POST .../compose/db/table/add)创建表,获得表 ID。
- 立刻调用
get-table 读出建表时自动生成的 8 个系统字段(id/uid/user_name/nick_name/agent_id/agent_name/created/modified),它们必须原样回传。
- 调用
update-table-definition(POST .../compose/db/table/updateTableDefinition)定义字段结构——必须传"系统字段(原样)+自定义字段"的完整 fieldList,不能只传新字段。详见下方「⚠️ update-table-definition 关键约束」。
- 再次调用
get-table 读取 dorisTable(物理表名,形如 custom_table_{表ID}),SQL API 的 SQL 语句里必须用这个物理表名,不能用建表时的中文表名(如"用户表")。
- 调用
table-sql-new(POST .../table/sql/new)创建数据表SQL操作API,用于页面组件调用。
- 如需修改已创建的SQL操作API,调用
table-sql-update(POST .../table/sql/update)。
- 【最易遗漏】前端对接 + 登记进
.project.md:建表/建 API 只是脚手架。建好后必须:①获取每个 SQL 操作 API 的端点 token;②按下方「⚠️ 前端对接规范(必读)」落地前端 lib;③把表 ID、物理表名、token、SQL、入出参全部登记进项目根目录的 .project.md(见下方「项目记忆 .project.md」)。前端真正能调用才算闭环。
⚠️ 不要用 copy-table 来"绕过"建表/加字段。update-table-definition 用对格式后完全能自建自定义字段(见下方约束)。复制别人的表会把无关字段带进来,造成字段语义错乱。
查询表信息
- 若不知道表 ID,先调用
list-tables 获取表列表。
- 调用
get-table(GET .../compose/db/table/detailById)获取表定义详情。
脚本封装:add_table / list_tables / update_table_definition / table_sql_new / table_sql_update 等;CLI:add-table、list-tables、table-sql-new、table-sql-update 等。
⚠️ update-table-definition 关键约束(必读,否则必然报错)
这是本技能最容易踩坑的接口。以下任一条件不满足,后端会返回 4000 JSON parse error 或 5000 系统开小差啦:
-
fieldType 必须是数字枚举,绝对不能传类型名字符串(如 "VARCHAR" / "INT")。合法枚举:
| fieldType | 类型 |
|---|
1 | VARCHAR |
2 | INT |
5 | DATETIME |
6 | BIGINT |
7 | MEDIUMTEXT / 长文本 |
-
必须传完整的 fieldList(系统字段 + 自定义字段),只传自定义字段会被拒。
- 先
get-table 读出系统字段,原样保留其 fieldType / defaultValue(时间字段是 CURRENT_TIMESTAMP)/ sortIndex / systemFieldFlag:true,再追加自定义字段。
- VARCHAR 字段需带
fieldStrLen(如 255),数值/时间字段 fieldStrLen 传 null。
-
**字段注释的 key 是 fieldDescription **。
-
每个字段需带完整属性:fieldName / fieldDescription / fieldType(数字) / fieldStrLen / nullableFlag / defaultValue / uniqueFlag / enabledFlag / sortIndex / systemFieldFlag。
可直接复制的正确 fieldList 模板
下面是已验证成功的完整请求体(系统字段原样 + 自定义字段),替换 id 为你的表 ID、自定义字段按需修改即可:
python scripts/datatable_for_page_api.py update-table-definition --id 398 --field-list '[
{"fieldName":"id","fieldDescription":"主键ID","fieldType":6,"fieldStrLen":null,"nullableFlag":false,"defaultValue":null,"uniqueFlag":false,"enabledFlag":true,"sortIndex":0,"systemFieldFlag":true},
{"fieldName":"uid","fieldDescription":"用户唯一标识","fieldType":1,"fieldStrLen":255,"nullableFlag":false,"defaultValue":null,"uniqueFlag":false,"enabledFlag":true,"sortIndex":1,"systemFieldFlag":true},
{"fieldName":"user_name","fieldDescription":"用户名","fieldType":1,"fieldStrLen":255,"nullableFlag":true,"defaultValue":null,"uniqueFlag":false,"enabledFlag":true,"sortIndex":2,"systemFieldFlag":true},
{"fieldName":"nick_name","fieldDescription":"用户昵称","fieldType":1,"fieldStrLen":255,"nullableFlag":true,"defaultValue":null,"uniqueFlag":false,"enabledFlag":true,"sortIndex":3,"systemFieldFlag":true},
{"fieldName":"agent_id","fieldDescription":"智能体唯一标识","fieldType":1,"fieldStrLen":255,"nullableFlag":true,"defaultValue":null,"uniqueFlag":false,"enabledFlag":true,"sortIndex":4,"systemFieldFlag":true},
{"fieldName":"agent_name","fieldDescription":"智能体名称","fieldType":1,"fieldStrLen":255,"nullableFlag":true,"defaultValue":null,"uniqueFlag":false,"enabledFlag":true,"sortIndex":5,"systemFieldFlag":true},
{"fieldName":"created","fieldDescription":"数据创建时间","fieldType":5,"fieldStrLen":null,"nullableFlag":false,"defaultValue":"CURRENT_TIMESTAMP","uniqueFlag":false,"enabledFlag":true,"sortIndex":6,"systemFieldFlag":true},
{"fieldName":"modified","fieldDescription":"数据修改时间","fieldType":5,"fieldStrLen":null,"nullableFlag":false,"defaultValue":"CURRENT_TIMESTAMP","uniqueFlag":false,"enabledFlag":true,"sortIndex":7,"systemFieldFlag":true},
{"fieldName":"title","fieldDescription":"标题","fieldType":1,"fieldStrLen":255,"nullableFlag":true,"defaultValue":null,"uniqueFlag":false,"enabledFlag":true,"sortIndex":8,"systemFieldFlag":false},
]'
排错提示:若仍报 5000,逐项自查——①是否误用了字符串 fieldType ②是否漏传系统字段 ③是否漏了 fieldStrLen。
⚠️ 前端对接规范(必读,建完 SQL API 后立刻照做)
table-sql-new / table-sql-update 只是把 SQL 操作 API 注册到平台;前端页面真正调用它时走的是另一条路径。这一步没有文档支撑是前后对接反复踩坑的根因,逐条核对:
0. 关键真相:SQL 操作 API 本质是工作流(Workflow)
通过本技能创建的「数据表 SQL 操作 API」,平台会把它实例化为一个 Workflow(工作流),工作流内部含一个 SQL 节点,真正去读写底层物理表(形如 custom_table_{表ID})。
- 前端页面调用时,调用的就是这个工作流。
- 每个这样的工作流都有一个唯一的端点 token(一串数字),前端 lib 靠「功能名 → token」映射表来调用。
- ⚠️ 该 token 通常不直接出现在创建接口的返回体里,需要从平台「SQL 操作 API / 工作流列表」中获取。取到后必须立即登记进
.project.md,下次无需再查找。
- ⚠️ 实际上
table-sql-new / table-sql-update 的返回体(data.apiSchema)里会带调用路径 .../api/v1/4sandbox/page/w/{token},末尾那串数字就是当前生效的 token —— 直接从这里抓即可,不必再去别处找。
0.1 ⚠️ 更新 SQL API 后,端点 token 会变(高频踩坑)
table-sql-update 成功后,平台经常会重新生成端点 token,旧 token 立即失效。
- 现象:只改了 SQL(或 args),前端却开始 404 / 调用失败 / 返回非预期结果,且
.project.md 里登记的还是旧 token。
- 每次 new/update 后的强制闭环(少一步都会断链):
- 从返回体
data.apiSchema 里抓最新 token(正则 page/w/(\d+));
- 立刻用新 token 覆盖
.project.md 对应条目;
- 立刻同步前端 lib 的「功能名 → token」映射;
- 用新 token 实际调一次验证通不通。
- 排错第一反应:前端报「调用失败 / 404 / 调不通」时,先查 token 是否在最近一次 update 后没刷新,再去查 SQL 语法。
1. 调用路径与端点 token
2. 响应外壳结构(查询类)
所有查询类 API 的返回都包在统一外壳里,业务数据在 data.outputList,不是 data 本身:
{
"success": true,
"message": "success",
"data": {
"outputList": [ { "id": 1, "...": "..." } ],
"rowNum": 1
}
}
前端解析:const rows = result.data?.outputList ?? []。务必先判 result.success,失败时抛 result.message。
3. 写操作(INSERT / UPDATE / DELETE)语义
- 写操作通常只返回
{ success: true },不回填新主键 id。
- 因此前端「新增」后不要用假 id 渲染单条,应直接整表/按条件重新拉取列表刷新。
- 批量删除/更新同理,按调用次数计数成功条数即可。
4. LIKE 模糊查询:用普通参数 {{var}} + 前端自拼 %
- 模糊查询统一用
{{var}} 普通参数占位符,不要用 ${{var}}。
- SQL 写法:
WHERE name LIKE {{keyword}}(和精确匹配 = 用同一种占位符)。
- 平台不会自动包
%,前端必须自己拼:传 %关键词%(前一个 %、后一个 %),例如搜「小美」就传 %小美%。
- 普通参数走参数化通道,值一定带引号,不会把关键词当成列名。
- ⚠️ 不要用
${{var}}:它在本平台展开后值常丢引号,生成 WHERE name LIKE 小美,MySQL 把关键词当列名,报 Unknown column 'xxx' in 'where clause'。
- 精确匹配用
={{var}},模糊匹配用 LIKE {{keyword}}(前端拼 %),二者不要混用。
5. 字段名对齐
- 建表字段名(
fieldName)= SQL 列名 = 响应 JSON 字段名,三者必须一致。
- 当后端列名与前端业务概念不同时(很常见),在前端
lib 层集中写一个 toXxx(raw) 映射函数做转换,组件层只消费映射后的统一类型,不要把映射逻辑散落到各个组件。
id 在后端是数值(bigint),前端建议统一 String(id) 处理,避免大整数精度问题。
- 系统字段
created/modified 是平台自动维护的 CURRENT_TIMESTAMP,建表时保留其 defaultValue,前端只读不写。
6. referer
页面同源调用时浏览器会自动带上 referer,一般无需手动处理。若在非页面环境(如脚本/SSR)调试页面端点,需手动带 referer: {PLATFORM_BASE_URL}/page/{PROJECT_ID}-/dev/,否则可能被拦。
项目记忆 .project.md(强制执行)
项目根目录维护一份 .project.md,作为 AI 的「项目记忆中枢」,集中沉淀项目设计、数据表结构、SQL 操作 API(工作流)及其端点 token 等关键信息。解决「端点 token 从哪来」反复踩坑的问题。
- 进入项目做任何开发前,必须先阅读
.project.md(不存在则创建)。
- 建表 / 建(或更新)SQL 操作 API 后,立即把表 ID、物理表名、端点 token、SQL、入出参登记进
.project.md,再开始写前端代码。
- 前端开发时,端点 token、字段名一律从
.project.md 取,不要凭记忆或猜测。
- 开发过程中只要产生了应长期保留的信息(改了字段、定了调用约定等),同步更新
.project.md。
- 它是唯一事实来源(single source of truth);只存「项目级、跨会话需保留」的信息,临时调试信息不要塞进来。
.project.md 建议登记模板
# 项目记忆(.project.md)
## 1. 项目概述
- 用途 / 技术栈 / 模版类型(react-vite / vue3-vite 等)
## 2. 数据表清单
### 表:<表名>
- 表 ID:<如 398>
- 物理表名(dorisTable):<如 custom_table_398>
- 字段:
| 字段 | 类型 | 系统/自定义 | 说明 |
|------|------|------------|------|
## 3. SQL 操作 API(工作流)清单
### <功能名,如 getAll / add / update / delete / search>
- 工作流 ID:<如平台分配的 workflow id>
- 端点 token:<前端 /api/page/w/{token} 用的那串数字>
- 绑定物理表:<如 custom_table_398>
- SQL 节点文本:
```sql
SELECT ... FROM custom_table_398 WHERE ...
- 入参:<参数名 / 是否必填 / 用途;LIKE 用 {{var}},前端自拼
%关键词%>
- 出参:查询类在 data.outputList;写操作仅回 success,不回填 id
4. 前端调用约定
- lib 文件位置:<如 src/lib/xxxTableApi.ts>
- 字段映射:<后端列名 ↔ 前端模型名>
- 写后刷新 / 跨表级联顺序等约定
5. 变更记录
## 跨表关联与级联更新(常见范式)
业务里常出现「主表某个字段引用另一张字典表」的情况(例如:业务表 A 有一个分类字段,值来自字典表 B 的名称)。改字典表名称时,必须**级联更新**业务表里所有引用它的行,否则出现脏数据。
### 范式
1. 为级联操作单独建一个 **UPDATE 类 SQL 操作 API**(不要复用普通更新 API),形如:
```sql
UPDATE custom_table_{A的表ID}
SET {关联字段} = {{newName}}
WHERE {关联字段} = {{oldName}}
-
前端「改名」流程要保证先级联、再改字典表的顺序:
1) 调用级联 UPDATE API,把业务表里 oldName 全部改成 newName
2) 调用字典表自身的 update API,改字典记录
3) 刷新业务列表(级联生效)
-
删除字典项同理:先级联把引用置为一个约定的默认值(如 "default" 或「未指定」),再删字典记录。
跨表没有外键约束,一致性完全靠应用层按上述顺序保证。把级联 API 和调用顺序登记进 .project.md,避免下次遗漏。
前端 lib 层最小模板(TypeScript)
新建业务时,前端 src/lib/xxxTableApi.ts 按下面骨架填写即可,已内含:端点 token 映射、统一外壳解析、outputList 取值、写后刷新约定、原始行→前端模型映射。字段全部替换为你的实际表结构。
const API_ENDPOINTS = {
getAll: '<<token>>',
getById: '<<token>>',
add: '<<token>>',
update: '<<token>>',
remove: '<<token>>',
search: '<<token>>',
} as const;
export interface YourItem {
id: string;
createdAt: string;
updatedAt: string;
}
interface RawRow {
id: number;
created?: string;
modified?: string;
}
interface ApiEnvelope<T> {
success: boolean;
message: string;
data?: { outputList: T[]; rowNum: number };
}
const API_PATH_PREFIX = '/api/page/w';
async function callAPI<T>(
token: string,
params: Record<string, unknown> = {}
): Promise<T> {
const res = await fetch(`${API_PATH_PREFIX}/${token}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const result: ApiEnvelope<T> = await res.json();
if (!result.success) throw new Error(result.message || 'API 调用失败');
return (result.data ?? ({} as T));
}
function toItem(r: RawRow): YourItem {
return {
id: String(r.id),
createdAt: r.created ?? '',
updatedAt: r.modified ?? '',
};
}
export async function getAll(): Promise<YourItem[]> {
const data = await callAPI<{ outputList: RawRow[] }>(API_ENDPOINTS.getAll);
return (data.outputList ?? []).map(toItem);
}
export async function search(query: string): Promise<YourItem[]> {
const data = await callAPI<{ outputList: RawRow[] }>(API_ENDPOINTS.search, { query: `%${query}%` });
return (data.outputList ?? []).map(toItem);
}
export async function add(input: Partial<YourItem>): Promise<void> {
await callAPI(API_ENDPOINTS.add, {
});
}
export async function update(id: string, input: Partial<YourItem>): Promise<void> {
await callAPI(API_ENDPOINTS.update, { id, });
}
export async function remove(id: string): Promise<void> {
await callAPI(API_ENDPOINTS.remove, { id });
}
调用方约定:页面组件 await add(...) 之后立即 await getAll() 重新拉取列表,不要依赖写操作的返回值渲染单条。
Authentication
The script reads authentication from environment variables:
| Variable | Description |
|---|
PLATFORM_BASE_URL | Platform API base URL (e.g. https://xxx) |
SANDBOX_ACCESS_KEY | Sandbox access key for API calls |
SANDBOX_ID | Sandbox identifier (optional; sent as X-Sandbox-Id when available) |
DEV_PROJECT_ID | 当前项目 ID(projectId),table-sql-new / table-sql-update 的 --project-id 参数从这里取值 |
DEV_SPACE_ID | 当前项目所在空间 ID(spaceId),add-table / list-tables 的 spaceId 由脚本从此变量自动读取并注入请求体,确保表建在/查在项目空间而非个人空间 |
Requests use:
Authorization: Bearer $SANDBOX_ACCESS_KEY
X-Sandbox-Id: $SANDBOX_ID (when set)
⚠️ 获取 projectId(禁止创建测试工作流)
table-sql-new 和 table-sql-update 需要 projectId 参数。直接从环境变量 DEV_PROJECT_ID 读取,不要用其他方式获取。
🚫 绝对禁止创建测试 SQL 工作流(如 SELECT 1)来探测 projectId 或验证 API 连通性。
- 每个通过
table-sql-new 创建的 SQL API 都会实例化为一个永久工作流,平台没有删除接口,测试工作流会永久残留在项目中。
- 正确做法:
projectId = os.environ.get('DEV_PROJECT_ID'),直接使用。
⚠️ 获取 spaceId(禁止使用个人空间)
add-table 和 list-tables 需要 spaceId 参数。脚本已自动从环境变量 DEV_SPACE_ID 读取并注入请求体,无需也禁止手动指定。
🚫 绝对禁止把表建到/查到用户个人空间。
- 平台后端在
spaceId 为空时,会回退到调用者的个人空间 ID,而个人空间不是项目所在空间——建到个人空间的表不会出现在项目里,前端 SQL API 也读不到。
- 正确做法:保证沙箱环境注入了
DEV_SPACE_ID(项目空间 ID),脚本会自动带上;不要通过修改脚本、手动构造请求体等方式绕过。
- 排错第一反应:表建完后在前端/列表里看不到,先确认
DEV_SPACE_ID 是否正确指向项目空间,而不是个人空间。
Python Script
The client script is located here:
datatable-for-page-api/scripts/datatable_for_page_api.py
Base URL
Table Definition endpoints are called under:
$PLATFORM_BASE_URL/api/v1/4sandbox/compose/db/table/...
Table SQL API endpoints are called under:
$PLATFORM_BASE_URL/api/v1/4sandbox/table/sql/...
For sandbox calls, spaceId in table-add and table-list requests is sourced automatically from the DEV_SPACE_ID environment variable by the script (see below). Never omit or override it — if spaceId is missing the backend falls back to the caller's personal space, which is NOT the project space and will create/query tables in the wrong place.
Supported Endpoints
Table Definition(表结构管理)
| CLI Command | Method | Endpoint | Description |
|---|
add-table | POST | /add | Create a new table definition(新增表定义) |
update-table-name | POST | /updateTableName | Update table name / description / icon(更新表名称和描述) |
update-table-definition | POST | /updateTableDefinition | Update table field definitions(更新表字段定义) |
delete-table | POST | /delete/{id} | Delete a table definition(删除表定义) |
list-tables | POST | /list | Query table definitions paginated(查询表定义列表) |
get-table | GET | /detailById?id= | Get table definition detail(查询表定义详情) |
exist-table-data | GET | /existTableData?tableId= | Check if table has business data(查询表是否有数据) |
copy-table | POST | /copyTableDefinition?tableId= | Copy a table structure(复制表结构) |
Table SQL API(数据表SQL操作API管理)
| CLI Command | Method | Endpoint | Description |
|---|
table-sql-new | POST | /api/v1/4sandbox/table/sql/new | Create a new SQL-based table operation API(创建数据表SQL操作API) |
table-sql-update | POST | /api/v1/4sandbox/table/sql/update | Update an existing SQL-based table operation API(更新数据表SQL操作API) |
Usage
python scripts/datatable_for_page_api.py --help
Table Definition Examples
python scripts/datatable_for_page_api.py add-table --name "Users" --description "User information table"
python scripts/datatable_for_page_api.py list-tables --page-no 1 --page-size 20
python scripts/datatable_for_page_api.py get-table --table-id 123
python scripts/datatable_for_page_api.py update-table-name --id 123 --name "Users_v2" --description "Updated user table"
python scripts/datatable_for_page_api.py update-table-definition --id 123 --field-list '[{"fieldName":"name","fieldType":1,"fieldStrLen":255,"fieldDescription":"user name","nullableFlag":true,"defaultValue":null,"uniqueFlag":false,"enabledFlag":true,"sortIndex":8,"systemFieldFlag":false},{"fieldName":"age","fieldType":2,"fieldStrLen":null,"fieldDescription":"user age","nullableFlag":true,"defaultValue":null,"uniqueFlag":false,"enabledFlag":true,"sortIndex":9,"systemFieldFlag":false}]'
python scripts/datatable_for_page_api.py copy-table --table-id 123
python scripts/datatable_for_page_api.py exist-table-data --table-id 123
python scripts/datatable_for_page_api.py delete-table --id 123
Table SQL API Examples
⚠️ SQL 语句里的表名必须用 get-table 返回的 dorisTable(物理表名,形如 custom_table_123),不能用建表时的表名(如"用户表")。
占位符:{{var}} 普通参数(LIKE 也用它,前端自拼 %关键词%)。
python scripts/datatable_for_page_api.py table-sql-new \
--project-id "100" \
--table-id 123 \
--api-name "queryUsers" \
--description "Query users by name" \
--sql "SELECT * FROM custom_table_123 WHERE name = {{name}}" \
--args '[{"name":"name","description":"user name","require":true}]'
python scripts/datatable_for_page_api.py table-sql-update \
--project-id "100" \
--api-id 456 \
--api-name "queryUsersV2" \
--description "Query users by name and age" \
--sql "SELECT * FROM custom_table_123 WHERE name = {{name}} AND age > {{age}}" \
--args '[{"name":"name","description":"user name","require":true},{"name":"age","description":"min age","require":false}]'