mcpField
P
postgresql-development-cloudbase

postgresql-development-cloudbase

github.com/tencentcloudbase数据与数据库7979 次安装今天更新
代码仓库

关于 postgresql-development-cloudbase

postgresql-development-cloudbase 是指导 CloudBase PostgreSQL 应用开发的技能,面向在 CloudBase PG 环境中构建 Web 应用或 CMS 的开发者。它强调先确认环境后端,再按 PG 专用 API、迁移、授权与 RLS 流程实现数据访问。

主要功能

  1. 01确认环境是否启用 PostgreSQL
  2. 02区分 PG 与 NoSQL API
  3. 03通过迁移工作流管理数据库结构
  4. 04配置授权与行级安全策略
  5. 05指导前端 CRUD 与存储实现

适用场景

  1. 01为 Web 应用配置 CloudBase PG
  2. 02通过迁移创建或变更表结构
  3. 03设置 authenticated 用户的数据访问
  4. 04排查 PG 环境与存储配置问题

MCP 相关

该技能指导通过 MCP 的 queryEnv 检查环境,并使用 queryPgDatabase 检查数据、managePgDatabase 管理数据库。它要求通过迁移工作流应用结构变更。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/tencentcloudbase/skills --skill postgresql-development-cloudbase

安装后在 Claude Code 中输入/postgresql-development-cloudbase确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

LICENSE.md
references/
app-workflow.md
auth-and-rls.md
http-api.md
index.md
pg-mode-overview.md
realtime.md
rls-patterns.md
storage-pg.md
troubleshooting.md

详细介绍

同级技能(仅本地)

同级 CloudBase 技能与此技能一起提供。使用 ../auth-tool-cloudbase/SKILL.md 这类本地相对路径。

如果此环境中缺少所引用的同级技能文件,请让用户安装完整的 CloudBase 插件(或缺少的技能)。不要通过 HTTP 将远程技能或协议 Markdown 拉取到 Agent 上下文中。

CloudBase PostgreSQL 开发

激活约定

以下情况请先使用本技能
  • 任务提到 CloudBase PG、PostgreSQL、Postgres、PG 模式、RLS、JS SDK v3 PostgreSQL、app.rdb()、queryPgDatabase 或 managePgDatabase。
  • Web 应用或 CMS 必须将业务数据持久化到 CloudBase PostgreSQL,而不是 NoSQL 或 MySQL。
接着还要阅读
  • 访问模式设计或慢查询工作 -> ../postgresql-best-practices-cloudbase/SKILL.md
  • Web 身份验证提供方准备情况 -> ../auth-tool-cloudbase/SKILL.md
  • Web 登录实现 -> ../auth-web-cloudbase/SKILL.md
  • 通用 Web 实现与验证 -> ../web-development/SKILL.md
  • 浏览器存储上传 -> ../cloud-storage-web/SKILL.md
  • 仅当 SDK 覆盖受阻时,才查阅原始 HTTP API 详情 -> ../http-api-cloudbase/SKILL.md
  • PG 参考索引 -> references/index.md
  • PG 模式概览 -> references/pg-mode-overview.md
  • 身份验证 / GRANT / RLS 详情 -> references/auth-and-rls.md
  • PG 应用端到端闭环 -> references/app-workflow.md
  • PG 存储详情 — 编写任何存储桶 / 上传 / URL 代码前必须阅读 -> references/storage-pg.md
  • HTTP API 备用方案 -> references/http-api.md
  • 故障排除 -> references/troubleshooting.md
不要首先使用
  • relational-database-mcp-cloudbase / queryMysqlDatabase / manageMysqlDatabase:这些面向 MySQL。
  • cloudbase-document-database-web-sdk / 集合 API:不要用于必须存放在 CloudBase PG 中的业务数据。

必需流程

🚨 关键:PG 模式 API 与 NoSQL API 不同

CloudBase PG(app.rdb()、app.storage.from('bucket'))使用的 API 方法名称与 CloudBase NoSQL(app.database()、app.uploadFile())不同。能力较弱的模型经常会从训练数据中套用旧版 NoSQL / 身份验证代码片段;请立即拒绝这种做法。如果任务由 PG 提供支持,不要编写 app.database()、db.collection(...)、app.uploadFile()、getLoginState() 或基于 auth.getUser() 的路由守卫。请改用 app.rdb()、PG storage v3 和 auth.getSession()。如果你习惯使用其他 ORM 或 NoSQL 中的 .where()、.orderBy()、.count(),请停下来阅读下表。

❌ 不要使用这些(NoSQL / ORM 习惯用法)✅ PG 模式下请使用这些
.where({ field: value }).match({ field: value }) 或 .eq("field", value)
.where("field", "ilike", "%v%").ilike("field", "%v%")
.orderBy("field", { ascending: false }).order("field", { ascending: false })
.count().select("*", { count: "exact" }) — 计数包含在响应中
.offset(n).range(from, to)
app.uploadFile()(旧版 NoSQL 上传)app.storage.from('bucket').upload(key, file)
app.getTempFileURL()(旧版 NoSQL URL)app.storage.from('bucket').createSignedUrl(key, expiresIn)
app.storage.from()(未指定存储桶名称)app.storage.from('bucket') — 必须传入存储桶名称

如果你发现自己正在输入 .where()、.orderBy() 或 .count(),请停下来,使用右栏中的正确方法。 0. 首先,确认当前环境确实已配置 PostgreSQL。 调用 queryEnv(action="info", envId=...),并读取派生出的 EnvInfo.RuntimeBackends 块({ postgresql, nosql, mysql })以及 EnvInfo.RuntimeMode。仅当 RuntimeBackends.postgresql === true(等价于 EnvInfo.PostgreSQL 非空,和/或 EnvInfo.Meta 包含 postgresql=enable)时,才能安全地应用本技能中针对 PG 的指导。

  • PG 模式是创建带 PostgreSQL 的 CloudBase 环境时选择的新环境模式。不要尝试原地将旧环境“升级”;请改为创建或选择 PG 模式环境。
  • 如果 RuntimeBackends.postgresql === false,请停止——这是仅支持 NoSQL 的旧环境:浏览器端数据请改用 cloudbase-document-database-web-sdk,上传请改用 cloud-storage-web(配合 app.uploadFile())。不要编写 app.rdb() 代码、启用 RLS,也不要在此处创建 pgstore 存储桶。
  • 如果 postgresql 和 nosql 均为 true(PG 环境中的常见情况),两者会共存。对于任务要求存入 PG 的新业务数据(例如明确要求使用 PG 的文章/角色表),请应用本技能。现有 NoSQL 集合、EnvInfo.Storages[] 中报告的存储桶,以及任何 managePermissions(resourceType="noSqlDatabase") 规则,仍然管理着旧版 NoSQL 数据——除非任务明确要求,否则不要迁移或重写这些内容。
  • RuntimeBackends.mysql === false 是唯一明确表示“禁止使用”的信号:如果没有 MySQL,请勿使用 manageMysqlDatabase/queryMysqlDatabase,也不要参考 relational-database-mcp-cloudbase 技能;这些内容仅适用于 MySQL,与 CloudBase PG 无关。
  • 注意:在 PG 环境中,EnvInfo.Storages[] 是旧版 NoSQL 存储桶。它仍可用于旧版 app.uploadFile() 流程,但不能作为可用的 pgstore 存储桶——绝不要在 app.storage.from('<bucket>').upload('<key>', file) 中复用它作为 <bucket> 部分。

创建 PG 模式环境

如果步骤 0 显示 RuntimeBackends.postgresql === false,但你需要 PostgreSQL,请创建一个启用了 PG 的新环境:

  • 通过 MCP:manageEnv(action="create", alias="my-env", packageId="baas_personal", resources=["storage","function","postgresql"], confirm="yes") — 可选的 region(例如 region="ap-shanghai")用于选择环境的创建位置;它会作为 X-TC-Region 请求上下文,因此不要在请求正文中填写 Region。省略此参数则使用当前会话区域(站点默认值:国内为 ap-shanghai,国际为 ap-singapore);如果传入此参数,请在确认调用时再次传入。
  • 通过 CLI:tcb env create --alias my-env --package baas_personal --postgresql --region ap-shanghai --yes
  • 通过控制台:创建环境
  1. 先检查现有的应用界面:src/lib/backend.*、src/lib/auth.*、src/lib/*service.*、路由守卫,以及绑定到现有表单的处理程序。

  2. 通过 MCP 检查 PG 状态:使用 queryPgDatabase 检查架构或读取数据,使用 managePgDatabase 执行 DDL/DML。不要切换到 MySQL 工具。要查看完整的路由映射,请阅读 references/index.md。

  3. 编写代码前先了解 PG 角色: Publishable Key 对应 anon;已登录用户的访问令牌对应 authenticated;API Key 对应 service_role,并会绕过 RLS。切勿在前端代码中暴露 API Key / service_role 凭据。请参阅 references/auth-and-rls.md。

  4. 编写 CRUD 代码前,先使用架构管理工具(managePgDatabase)。 架构 DDL(CREATE / ALTER / DROP / TRUNCATE)必须通过版本化迁移工作流执行——创建表时,绝不能默认使用 execute。然后再应用 GRANT + RLS(通过 execute 或同一个迁移 SQL 包),之后才能通过浏览器访问。SQL 包至少应包含:CREATE TABLE、GRANT SELECT/INSERT/UPDATE/DELETE TO authenticated、使用 serial/bigserial 时的 GRANT USAGE, SELECT ON SEQUENCE ... TO authenticated、ALTER TABLE ... ENABLE ROW LEVEL SECURITY,以及 CREATE POLICY ... USING / WITH CHECK。完整模板请参阅 references/auth-and-rls.md。

    默认架构变更工作流(先写本地文件,再写入远程历史):

    1. 将 migrationVersion 设为 14 位 UTC 时间戳 YYYYMMDDHHMMSS,将 migrationName 设为 snake_case(例如 add_users)。
    2. 将 DDL 写入本地文件 cloudbase/migrations/<migrationVersion>_<migrationName>.sql(可选的回滚 SQL 可写在注释中或配套文件中)。此路径必须与 CloudBase CLI MIGRATIONS_DIR(tcb db pg migration *)一致。如果旧工作区仍使用根目录下的 migrations/,请在混用 MCP 和 CLI 之前将其中的文件移至 cloudbase/migrations/。
    3. 可选预览:managePgDatabase(action=planMigration, migrationName=..., migrationVersion=..., sql=...)。
    4. 应用:managePgDatabase(action=applyMigration, migrationName=..., migrationVersion=..., sql=..., confirm=true) — 使用与本地文件相同的版本号和名称。如果本地文件不存在,MCP 会自动写入 cloudbase/migrations/<version>_<name>.sql;如果现有文件的内容与 sql 不一致,应用操作会安全失败(LOCAL_MIGRATION_FILE_MISMATCH),且不会 Push。默认情况下,MCP 会等待异步任务完成(最多 10 分钟,与 CLI 相同);如果主机工具调用超时较短,可通过 taskPollTimeoutMs 调整超时时间,或设置 waitForTask=false。
    5. 验证:managePgDatabase(action=listMigrations),确认远程历史记录了相同的 migrationVersion。
    6. 然后再编写前端 CRUD / RLS 检查。

    乱序 / 回填版本: 优先使用严格晚于 LatestVersion 的 migrationVersion。如果必须应用早于 Latest 的版本(分支合并 / cherry-pick),请在 planMigration / applyMigration 中传入 includeAll=true — 与 CLI tcb db pg migration up --include-all 相同。日常工作中不要使用此选项。

    如果 applyMigration 返回 MIGRATION_TASK_TIMEOUT 或 MIGRATION_TASK_PENDING: 任务可能仍在运行(大型 DDL / 锁等待)。请先调用 describeMigrationTask(taskId=...),查看 Status/Phase/Reason,然后调用 listMigrations。不要再次 Push 相同的 migrationVersion;在任务结束且 listMigrations 确认该版本从未应用之前,不要回退到 execute。 其他迁移操作:

    • managePgDatabase(action=migrationDetail, migrationVersion=...) — 检查单个迁移
    • managePgDatabase(action=fetchMigration) — 将远程历史 SQL 拉取到 cloudbase/migrations/(与 CLI tcb db pg migration fetch 保持一致)。可选指定 migrationVersion 以获取单个文件;省略则获取完整历史记录。除非设置 force=true(覆盖文件/重新校准校验和),否则会跳过已有的本地文件。建议使用此操作,而不是手动从 migrationDetail 复制 SQL,以避免校验和不一致。
    • managePgDatabase(action=repairMigration, migrationVersion=..., migrationName=..., repairStatus=..., repairReason=...) — 修复历史记录

    execute 用于 DML 和运维 SQL,默认不用于 DDL: 使用 managePgDatabase(action=execute, confirm=true) 执行 INSERT/UPDATE/DELETE,以及不属于迁移的 GRANT/CREATE POLICY/存储 RLS 操作。如果通过 execute 尝试执行架构 DDL,工具会软性拦截并提示 DDL_USE_APPLY_MIGRATION,除非你显式设置 allowDdlViaExecute=true(仅作为逃生通道)。 🚨 严重:在执行 CREATE TABLE 前检查表是否存在以及列名。 如果表已存在,CREATE TABLE IF NOT EXISTS 会静默跳过,即使列名有误也一样。务必先调用 queryPgDatabase(action="sql", sql="SELECT column_name, data_type FROM information_schema.columns WHERE table_name='xxx'"),检查表是否存在以及实际使用的准确列名。如果表已存在,但列名不匹配(例如使用 user_id 而不是 uid),则必须:

    • 通过 applyMigration 并使用新的版本执行 ALTER TABLE,添加/重命名/删除列;或
    • 使用 DROP TABLE IF EXISTS ... CASCADE,然后通过 applyMigration 重新创建表(仅当数据丢失可以接受时,例如在一次性/评估环境中)。
    • 不要依赖 CREATE TABLE IF NOT EXISTS 的静默跳过行为——这会导致所有下游 CRUD 查询因字段名错误而失败。
    • 执行 DDL 后,重新查询架构,并核对前端代码、插入/更新负载、筛选条件、排序和 RLS 策略中使用的每个列名。
  5. 编写登录代码前,先检查用户名-密码认证:

    • 调用 queryAppAuth(action="getLoginConfig")。
    • 如果 loginMethods.usernamePassword !== true,调用 manageAppAuth(action="patchLoginStrategy", patch={ usernamePassword: true })。
    • 在 Web 登录代码中,对 admin 或 editor 这类普通用户名使用 auth.signInWithPassword({ username, password })。
    • 不要假设 auth.signUp({ username, password }) 可以直接创建用户名/密码用户。先确认 queryAppAuth 的 sdkHints 和已安装的 @cloudbase/js-sdk 的行为;如果不支持直接通过用户名注册,应通过后端/管理边界实现注册,而不是在浏览器中暴露密钥。
  6. 在编写 CRUD 前,使用 auth.getSession() 实现 Web 认证状态:

    • 路由守卫必须检查 data.session,不能使用 auth.getUser() 或已弃用的 getLoginState()。
    • 仅当 signInWithPassword(...) 返回的结果没有 error 且包含 data.session 时,才将登录视为成功。
    • 从 data.session.user.id 获取 author_id/角色行的 UID(仅在检查实际 session 对象后,才回退使用 sub/uid)。
    • 不要使用 auth.getUser() 作为登录凭证;没有真实的用户名/密码 session 时,它可能返回非空包装对象或看似匿名的用户数据。
  7. 浏览器端业务数据应优先使用 CloudBase JS SDK v3 的 PostgreSQL API:app.rdb().from(table)。如果缺少 app.rdb(xxx.rdb is not a function 意味着 SDK 版本过旧),请使用最新版 @cloudbase/js-sdk。

  8. 不要在浏览器代码中手动获取 CloudBase Auth Bearer 令牌用于 PG CRUD。尤其不要调用 currentUser.getIdToken() 等非标准辅助方法,除非已确认该方法在已安装的 SDK 中确实存在。优先使用 app.rdb(),由 SDK 传递当前有效 session。

  9. 在策略中使用官方 CloudBase PG SQL 认证辅助函数:使用 auth.uid() 获取 JWT sub,使用 auth.role() 获取 anon/authenticated/service_role,使用 auth.jwt() 获取完整声明,并在需要电子邮件时使用 auth.email()。仍须通过真实应用 session 验证策略,再确认其可用:

    • 通过真实应用路径登录。
    • 使用 author_id = session.user.id 插入一条测试行。
    • 使用 queryPgDatabase 读取该行。
    • 如果 INSERT/SELECT 失败,检查具体的 RLS 错误,并修复策略或改用服务端/RPC 边界。不要让面向浏览器的表保留无法正常工作的 RLS。
    • ⚠️ auth.uid() 返回 text,而不是 uuid。 所有者列优先使用 varchar(64)/text。如果要与 uuid 列比较,请使用 auth.uid()::uuid(仅当 JWT sub 是有效 UUID 时),否则会出现 operator does not exist: uuid = text。这与 Supabase 不同。请参阅 references/auth-and-rls.md。
    • ⚠️ 不要在 RLS 策略中使用 current_user 或 current_setting(...)。 PostgreSQL 中的 current_user 返回数据库角色名(例如 authenticated),而不是 CloudBase 认证用户 ID。检查用户身份时始终使用 auth.uid()。如果不确定是否有认证辅助函数可用,请运行 SELECT proname FROM pg_proc WHERE pronamespace = 'auth'::regnamespace,列出所有可用的 auth.* 函数。
  10. 只有在阅读 OpenAPI 文档并确认已安装 SDK 中的认证模型后,才将 PG HTTP API 用作回退方案。不要猜测 /api/v1/rdb/rest 这类 URL;文档指定的基址是 https://<envId>.api.tcloudbasegateway.com/v1/rdb/rest/<table>,认证方式为 Authorization: Bearer <Publishable Key | access_token | API Key>。

  11. 封面图片存储在 CloudBase Storage 中。PG 中仅保存最终文件 URL 或文件元数据。

  12. 声称完成前,验证两个层面:项目构建/类型检查,以及登录/CRUD 的浏览器 E2E;然后使用 queryPgDatabase 回读数据行。调试 RLS 时,如果工具支持角色模拟,请以 authenticated/anon 身份运行 SQL;管理员/默认执行可能会绕过面向用户时遇到的故障。

探索预算

  • 优先优化可正常使用的用户流程,再进行广泛研究。
  • 如果任务是使用 PG 支持 CRUD 的 Web 应用,请先阅读 references/app-workflow.md,并遵循其中的闭环流程,然后再查询可选的 HTTP API 详情。
  • 针对同一个问题,查询同一文档系列的次数不得超过两次。如果第二次查询仍无法解决问题,请检查已安装的 SDK 接口,或查看具体的运行时错误。
  • 一旦选择使用 app.rdb() 实现浏览器 CRUD,除非 app.rdb() 不存在或经证明确实失败,否则停止研究原始 PG HTTP API。
  • DDL 执行失败后,最多重试 SQL 两次。然后调用 queryPgDatabase(action="objects") 查找带模式限定的表名,再调用 queryPgDatabase(action="schema", objectName="public.your_table"),查看确切错误,并简化模式或权限方案。
  • 针对性修复时,避免陷入冗长的任务管理循环。读取当前文件,执行最少的平台设置,编辑代码并进行验证。
  • 文件读取预算:同一文件不得读取超过 2 次。读取 2 次后,如果还需要重新读取,请使用 Grep 进行定向搜索,或使用带有明确 offset/limit 的 Read 指定行范围。转而编辑或验证,不要继续重读文件。

数据模型规则

  • 使用 CloudBase Auth / CloudBase PG 内置的身份验证身份作为用户来源。除非应用需要,否则不要额外复制一张身份表。

  • 如果应用需要管理员/编辑者行为,请将业务角色保存在 PG 中,例如使用包含 uid、username 和 role 的 user_roles。uid 的值必须与 Web 会话用作 session.user.id 的值相同,并且必须与使用的任何数据库策略表达式相匹配。

  • 将内容表保存在 PG 中,例如包含所有者 UID 列的 articles 或 posts。

  • PG 表的物理列名优先使用 snake_case(author_id、author_name、cover_image、created_at、updated_at)。如果 UI 字段使用 camelCase,请在服务边界处显式映射。

  • 将 queryPgDatabase(action="schema", objectName="public.your_table") 返回的架构视为事实来源。objectName 为必填项,且必须包含架构限定名;如果尚不知道它,请先调用 queryPgDatabase(action="objects")。如果现有表包含 authorid/updatedat,要么在代码中使用这些确切列名,要么先显式迁移/删除并重建该表,再编写依赖 author_id/updated_at 的代码。

  • CREATE TABLE IF NOT EXISTS 不会更改已有的不兼容架构。在评估或可丢弃的环境中,如果需要确定的架构,优先有意执行 DROP TABLE IF EXISTS ... CASCADE,然后执行 CREATE TABLE ...。

  • 执行 DDL 后,再次查询表架构,并逐一核对前端代码、插入/更新负载、筛选条件、排序和 RLS 策略中使用的每一列。

  • 数据库或服务/RPC 层必须实现后端权限控制。仅在 UI 中隐藏按钮是不够的。

  • 不要让面向浏览器的表启用 RLS 却没有任何策略。在这种状态下,PostgreSQL 默认会拒绝用户读取/写入,因此 app.rdb().from("articles").insert(...) 可能失败,而 UI 只显示通用的保存失败提示。如果启用 RLS,请在测试应用之前创建并验证 SELECT/INSERT/UPDATE/DELETE 策略。

  • 在策略中使用 CloudBase PG 官方 SQL 身份验证辅助函数:auth.uid()(JWT sub,返回 text 而不是 uuid)、auth.role()(anon/authenticated/service_role)、auth.jwt()(完整声明)以及适用时的 auth.email()。优先使用 owner_id varchar(64) DEFAULT auth.uid() 这样的所有者列,由数据库而非浏览器分配所有权。如果所有者列已是 uuid,请使用 auth.uid()::uuid 进行比较(仅当 sub 是有效 UUID 时)。

  • 标准所有者表模板——将此结构用于任何用户所有的业务表:

    CREATE TABLE articles (
      id          BIGINT      GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
      owner_id    TEXT        NOT NULL DEFAULT auth.uid(),
      title       TEXT        NOT NULL,
      created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
    );
    
    • owner_id 是 TEXT,不是 uuid——auth.uid() 返回文本(例如 EchhGXFadSANiCSaVim2wQ);将其声明为 uuid 会在建表时因类型不匹配而失败。
    • owner_id 带有 DEFAULT auth.uid()——所有权由服务端决定。应用代码不得传入该值;INSERT 策略会拒绝伪造的所有者值。
  • 填充演示数据: RLS 会拒绝匿名浏览器写入。请通过管理面插入演示/种子行——使用 managePgDatabase(action="execute", confirm=true) 并提供显式的 owner_id 值(例如 'system-demo');种子行不要依赖 DEFAULT auth.uid(),也绝不要在前端代码中加入种子 INSERT。

  • 如果需要详细的 GRANT/RLS 规则,请在编写策略前阅读 references/rls-patterns.md。

  • 对于管理员/编辑者工作流,请允许 admin 操作所有行,并仅允许 editor 操作所有者 UID 与当前用户匹配的行。

JS SDK v3 PostgreSQL 模式

表名规则(重要):

  • ✅ db.from("articles") — 推荐
  • ✅ db.from("public.articles") — 也有效(单个 schema 前缀)
  • ❌ db.from("public.public.articles") — 错误,重复添加 schema 前缀,会因 PGRST205 失败
  • queryPgDatabase() 中的 objectName="public.articles" 是 MCP 工具格式——不要将其复制到 db.from() 中。

使用静态导入和一个共享的 app.rdb() 客户端(SDK 初始化参考:webv3/initialization.md):

import cloudbase from "@cloudbase/js-sdk";
const app = cloudbase.init({
  env: import.meta.env.VITE_CLOUDBASE_ENV_ID,
  accessKey: import.meta.env.VITE_PUBLISHABLE_KEY, // publishable key, see auth-web-cloudbase prerequisites
  auth: { detectSessionInUrl: true },
});
export const auth = app.auth;
export const db = app.rdb();

最简身份验证辅助方法——只使用 auth.getSession(),绝不使用 auth.getUser():

async function getActiveSession() {
  const { data, error } = await auth.getSession();
  if (error || !data?.session || data.session.user?.is_anonymous) return null;
  return data.session;
}

标准 CRUD 形式(请照原样复制):

// READ
const { data, error } = await db.from("articles").select("*");
// CREATE — omit owner_id/author_id when the table defines DEFAULT auth.uid()
const { data, error } = await db.from("articles").insert({ title, status: "draft" });
// UPDATE
await db.from("articles").update({ status }).eq("id", id);
// DELETE
await db.from("articles").delete().eq("id", id);
// RPC
const { data } = await db.rpc("function_name", { id });

常用查询辅助方法:.eq()、.neq()、.gt()、.gte()、.lt()、.lte()、.like()、.ilike()、.in()、.is()、.contains()、.textSearch()、.or()、.not()、.match()、.order()、.limit()、.range()、.single()。

完整指南(官方 webv3 API——请直接复制,不要从 .d.ts 重新推导)。 来源:webv3/postgresql/fetch.md——fetch / insert / update / delete / upsert / filters / modifiers / rpc 共用相同的路径前缀,每种操作各有一个页面(在 URL 后加 .md 可获取原始 Markdown):

// COUNT only — no rows returned, count comes back on the result object
const { count, error } = await db.from("articles").select("*", { count: "exact", head: true });

// Pagination — .range(from, to) is INCLUSIVE on both ends; page 2 of 20 = .range(20, 39)
const { data, error } = await db.from("articles").select("*")
  .order("created_at", { ascending: false }).range(0, 19);

// INSERT and return the inserted row — ⚠️ .select() only returns rows when the
// table has a single auto-increment primary key; otherwise data is empty/null
const { data, error } = await db.from("articles").insert({ title, status: "draft" }).select();

// INSERT many rows at once (array form)
await db.from("articles").insert([{ title: "a" }, { title: "b" }]);

// UPSERT — include the primary key in values; onConflict names the unique-index column(s)
await db.from("articles").upsert({ id: 1, title: "new" }, { onConflict: "id" });

// Join query — PostgREST embedded resources via FK relationship
const { data, error } = await db.from("articles").select(`
  title,
  categories ( name ),
  created_by:users!articles_created_by_fkey ( name ) // multiple FKs to the same table need the constraint name
`);

// RPC — SETOF-returning functions chain .select()/.order()/.limit()/.single()/filters like a query
const { data, error } = await db.rpc("search_articles", { keyword })
  .select("title, published_at").order("published_at", { ascending: false }).limit(5);
const { data: one } = await db.rpc("search_articles", { keyword }).limit(1).single();
const { count } = await db.rpc("search_articles", { keyword }, { count: "exact", head: true });
⚠️ 重要:PG API 与 CloudBase NoSQL 或其他 ORM 并不相同

CloudBase PG(app.rdb())使用 postgREST 风格的查询辅助方法,不使用 CloudBase NoSQL(app.database())API,也不遵循常见的 ORM 约定。请勿使用:

❌ 错误(NoSQL / ORM 习惯)✅ 正确(postgREST / PG)
.where({ field: value }).match({ field: value }) 或 .eq("field", value)
.where("field", "ilike", "%v%").ilike("field", "%v%")
.orderBy("field", { ascending: false }).order("field", { ascending: false })
.count().select("*", { count: "exact" }) — 使用 select 的 count 参数获取总数,返回结果中会包含 count 字段
.offset(n).range(from, to) — 注意,range 分页包含两端

黄金法则:app.rdb() 的查询链只使用上方“常用查询辅助方法”中列出的方法。如果你习惯写 .where() / .orderBy() / .count(),请立即改用对应的 postgREST 方法。Supabase 的 @supabase/postgrest-js 同样不使用这些方法名。

Storage(v3):使用 app.storage.from('<bucket>').upload('<key>', file)——复制前请先检查已安装 SDK 提供的接口:

const { data } = await app.storage.from('covers').upload(`${file.name}`, file);
存储桶必须存在(与 Supabase 保持一致)

CloudBase PG 存储使用 pgstore 后端,并遵循与 Supabase Storage 相同的模式:每次上传都必须指定一个已存在的存储桶。浏览器 SDK 无法创建存储桶。在编写任何上传代码之前:

  1. 确认目标前缀(例如 covers)对应的 pgstore 存储桶可用。DescribeEnvs.Storages[] 暴露的旧版 NoSQL 存储桶(例如 6d63-…-1409864723)供旧版 NoSQL 后端使用,无法用于 pgstore 上传。
  2. 如果没有可用的存储桶,请通过 PG 存储管理界面(PG 存储 HTTP API/CLI/控制台/适用时使用 storage.buckets 上的 SQL)创建。不要假设传统模式的存储工具,或将 covers/ 添加为 JS 路径前缀,就能创建 PG 存储桶。
  3. 存储桶名称应写在 from('<bucket>') 中;传给 upload(key, file) 的键位于该存储桶内,不得重复存储桶前缀。正确:app.storage.from('covers').upload('a.png', file)。错误:app.storage.from('covers').upload('covers/a.png', file)。
  4. 创建存储桶后,通过 managePgDatabase(action="execute", confirm=true) 配置 storage.objects 上的 RLS。默认 RLS 会拒绝所有访问;如果没有宽松策略,浏览器会收到 STORAGE_PERMISSION_DENIED。完整的存储桶和 RLS 模板(用户隔离和公开读取存储桶)请参阅 references/storage-pg.md,确切的 SQL 策略请参阅 cloud-storage-web/SKILL.md 中的“存储桶创建后:存储 RLS”部分。

故障模式速查表(在 DevTools 网络标签页中查看失败的 POST .../v1/storages/get-objects-upload-info 请求):

/v1/storages/get-objects-upload-info 返回的 code含义修复方法
STORAGE_BUCKET_NOT_FOUND此 PG 环境中不存在路径里的存储桶。通过管理界面创建存储桶,然后重试。
STORAGE_PERMISSION_DENIED存储桶存在,但 storage.objects 上的 RLS 阻止了上传。运行 managePgDatabase(action="execute", confirm=true) 配置存储 RLS。请参阅 cloud-storage-web/SKILL.md 中的“存储桶创建后:存储 RLS”。
存储桶/键对应的 INVALID_PARAMSDK/API 收到的存储桶/键组合无效(例如 from() 未提供存储桶,或键为空)。使用 app.storage.from('covers').upload('a.png', file);存储桶写在 from() 中,键写在 upload() 中。
STORAGE_CONTENT_LENGTH_REQUIRED代码移除或遗漏了签名请求头 Content-Length。为 uploadFile 传入 headers: { 'Content-Length': String(file.size) },或使用 app.storage.from('<bucket>').upload('<key>', file) 并传入 Blob/File,让 SDK 自动填充。

如果在 DevTools 中看到 PUT https://undefined/ 和 net::ERR_NAME_NOT_RESOLVED,这就是上述三种情况之一的症状——上游元数据响应没有 uploadUrl 字段,SDK 因此将 undefined 传给后续的 PUT 请求。务必先检查上游 get-objects-upload-info 响应;不要追查 https://undefined/ 这个 URL 本身。

硬性规则:绝不能让上传错误被静默吞掉。如果 uploadCoverImage() 被拒绝,外层的 createArticle() 流程也必须被拒绝——不要使用虚构的封面 URL 向 PG 插入数据,不要显示成功提示,也不要使用猜测的存储桶名称重试。

ExecutePGSql / DDL 故障排查

  • ExecutePGSql / managePgDatabase(action="execute") 是管理/控制平面路径。不要在前端代码中暴露腾讯云 SecretKey 或 CloudBase API Key。
  • 每次调用执行一条 SQL 语句。请显式拆分批次,而不是发送以分号连接的多条语句。
  • 部分 DDL(CREATE / ALTER / DROP / GRANT / REVOKE / TRUNCATE / COMMENT)可能直接因瞬时 InternalError 而失败。发生这种情况时,请重试一次:将 DDL 包装在 DO $$ BEGIN EXECUTE '...'; END $$ 中,并转义字符串中的单引号。
  • 验证权限时,如果工具/API 支持角色参数,请使用面向用户的角色(anon 或 authenticated)。默认的管理员执行可能会掩盖缺失的 GRANT/RLS 策略。

HTTP API 回退方案

  • PG HTTP API 属于 CloudBase 关系型数据库 HTTP API 系列,与 MySQL 同属一类。在 MCP 文档/搜索中,它归在 mysqldb 下。
  • 编写原始 fetch() 代码之前,请先查询 OpenAPI 文档:searchKnowledgeBase(mode="openapi", apiName="mysqldb", query="PostgreSQL fetch insert update rpc")。
  • 不要凭记忆构造 REST 路径。猜测的路径返回 404 即构成阻断;请查阅表端点(fetch / insert / update / delete)的 OpenAPI 契约。
  • 数据库函数(RPC) 可用于执行原子化的服务端操作,例如带保护条件的计数器、条件更新或多步写入;普通 REST 更新无法安全实现这些操作。端点为与表端点使用同一网关基址的 POST /v1/rdb/rest/rpc/<function>;它不属于 mysqldb OpenAPI 文档,因此在写入代码前,请先通过一次实际调用确认。
  • 如果环境变量中提供了 TCB_HTTP_API_BASE_URL / VITE_TCB_HTTP_API_BASE_URL,请仅将其视为基址。路径、方法、请求头和认证模型仍须来自 OpenAPI 文档或现有的可用辅助函数。

前端防护措施

避免动态辅助函数陷阱:

  • 不要编写 function getAuth() { return (await import("./backend")).auth; };请使用顶层静态导入,或将函数声明为 async。
  • 在 Vite 中不要编写 typeof import !== "undefined";请直接使用 import.meta.env。
  • Vite 报告转换错误后,不要继续编辑。先修复语法,重新运行构建,然后测试浏览器流程。
  • 如果存在文档齐全的 v3 API,就不要花时间逆向分析无关的 SDK 内部实现。请先使用文档说明的 app.rdb() / app.storage.from() API。

快速检查

  • PG 架构已创建,且与服务代码一致。
  • 已启用用户名登录,代码使用用户名 API,而不是电子邮件 API。
  • 数据写入通过 JS SDK v3 app.rdb() 或有文档说明的 HTTP API 路径到达 CloudBase PG,而不是依赖本地状态、模拟数组或猜测后返回 404 的端点。
  • 浏览器中的 PG 代码不得依赖 user.getIdToken() 或虚构的令牌辅助函数。如果无法避免原始 HTTP,请先检查已安装的 CloudBase Web SDK/认证 API,并验证请求能通过当前用户会话成功执行。
  • 编辑权限在 UI 之外强制执行。
  • 与上传路径(例如 covers)匹配的 pgstore 存储桶必须在任何浏览器上传开始前创建。如果尚未创建,请通过管理界面创建;v3 SDK 不会替你创建。
  • 存储上传会返回可用 URL,且该 URL 会与文章一同持久化。上传错误必须向上传播;不要使用占位封面 URL 插入文章行。

参考索引

所有打包的参考文件(技能 lint 可达性所必需):

评论

支持 Markdown