postgresql-development-cloudbase について
postgresql-development-cloudbaseは、CloudBase PostgreSQLアプリ開発を案内するスキルです。CloudBase PGでWebアプリやCMSを構築する開発者向けに、環境確認からPG専用API、マイグレーション、権限、RLSによるデータアクセスまでを扱います。
主な機能
- 01環境でPostgreSQLが有効か確認
- 02PGとNoSQLのAPIを区別
- 03マイグレーションでスキーマ管理
- 04権限と行レベルセキュリティの設定
- 05フロントエンドのCRUDとストレージ実装を案内
活用シーン
- 01Webアプリ向けCloudBase PGの設定
- 02マイグレーションによるテーブル作成・変更
- 03authenticatedユーザーのデータアクセス設定
- 04PG環境やストレージ設定のトラブル対応
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.mdreferences/app-workflow.mdauth-and-rls.mdhttp-api.mdindex.mdpg-mode-overview.mdrealtime.mdrls-patterns.mdstorage-pg.mdtroubleshooting.md詳細
同階層のスキル(ローカルのみ)
同階層の CloudBase スキルは、このスキルと並んで配置されています。../auth-tool-cloudbase/SKILL.md などのローカル相対パスを使用してください。
参照先の同階層スキルファイルがこの環境に存在しない場合は、CloudBase プラグイン一式(または不足しているスキル)のインストールをユーザーに依頼してください。リモートのスキルやプロトコルの Markdown を HTTP で取得して、エージェントのコンテキストに取り込まないでください。
CloudBase PostgreSQL 開発
有効化の条件
次の場合は、まずこのスキルを使用してください
- タスクで CloudBase PG、PostgreSQL、Postgres、PG モード、RLS、JS SDK v3 PostgreSQL、
app.rdb()、queryPgDatabase、managePgDatabaseが指定されている場合。 - Web アプリまたは CMS で、NoSQL や MySQL ではなく CloudBase PostgreSQL に業務データを永続化する必要がある場合。
続けて以下も読んでください
- アクセスパターンの設計または遅いクエリへの対応 ->
../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 PG に保存する必要がある業務データに対する
cloudbase-document-database-web-sdk/コレクション API。
必須の手順
🚨 重要:PG モードの API は NoSQL と同じではありません
CloudBase PG(app.rdb()、app.storage.from('bucket'))では、CloudBase NoSQL(app.database()、app.uploadFile())とは異なる API メソッド名を使用します。能力の低いモデルは、学習データに含まれる古い NoSQL/認証のコード例を貼り付けがちです。その方法は直ちに却下してください。このタスクが PG を使用する場合は、app.database()、db.collection(...)、app.uploadFile()、getLoginState()、または auth.getUser() に基づくルートガードを使用しないでください。代わりに app.rdb()、PG ストレージ 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 を確認してください。このスキルの PostgreSQL 固有のガイダンスを適用して安全なのは、RuntimeBackends.postgresql === true(同等の条件は、EnvInfo.PostgreSQL が空でなく、かつ/または EnvInfo.Meta に postgresql=enable が含まれていること)の場合だけです。
- 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- コンソール経由:環境を作成
-
まず既存のアプリの各所を確認します。
src/lib/backend.*、src/lib/auth.*、src/lib/*service.*、ルートガード、既存のフォームに紐付けられたハンドラーを調べてください。 -
MCP を通じて PG の状態を確認します。スキーマの確認や読み取り専用の調査には
queryPgDatabaseを使い、DDL/DML にはmanagePgDatabaseを使ってください。MySQL ツールに切り替えないでください。ルートマップ全体については、references/index.mdを読んでください。 -
コードを書く前に PG のロールを理解してください。 Publishable Key は
anonに対応します。ログイン済みユーザーのアクセストークンはauthenticatedに対応します。API Key はservice_roleに対応し、RLS をバイパスします。API Key/service_roleの認証情報をフロントエンドのコードに決して公開しないでください。references/auth-and-rls.mdを参照してください。 -
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を参照してください。標準のスキーマ変更ワークフロー(ローカルファイルを先に作成し、その後リモートの履歴に適用):
migrationVersionには 14 桁の UTC タイムスタンプYYYYMMDDHHMMSSを、migrationNameにはスネークケース(例:add_users)を指定します。- DDL を記述したローカルファイル
cloudbase/migrations/<migrationVersion>_<migrationName>.sqlを作成します(ロールバック SQL はコメントまたは別ファイルとして任意で追加できます)。このパスは CloudBase CLI のMIGRATIONS_DIR(tcb db pg migration *)と一致している必要があります。古いワークスペースにルートのmigrations/が残っている場合は、MCP と CLI を併用する前に、そのファイルをcloudbase/migrations/に移動してください。 - 任意のプレビュー:
managePgDatabase(action=planMigration, migrationName=..., migrationVersion=..., sql=...)。 - 適用:
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に設定してください。 - 確認:
managePgDatabase(action=listMigrations)を実行し、リモートの履歴に同じmigrationVersionが記録されていることを確認します。 - その後、フロントエンドの CRUD/RLS チェックを作成します。
順序が前後するバージョン/バックフィルのバージョン:
LatestVersionより厳密に新しいmigrationVersionを優先してください。Latest より古いバージョンを適用する必要がある場合(ブランチのマージ/チェリーピック)は、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を呼び出してください。同じmigrationVersionを再 Push しないでください。また、タスクが終了状態になり、対象のバージョンが適用されていないことを list で確認するまでは、executeに切り替えないでください。 その他の移行操作:managePgDatabase(action=migrationDetail, migrationVersion=...)— 個別の移行を確認しますmanagePgDatabase(action=fetchMigration)— リモートの履歴 SQL をcloudbase/migrations/に取得します(CLI のtcb db pg migration fetchと同等です)。1 つのファイルを取得する場合はmigrationVersionを指定し、履歴全体を取得する場合は省略します。既存のローカルファイルは、force=true(上書き/チェックサムの再調整)を指定しない限りスキップされます。チェックサムのずれを避けるため、migrationDetailの SQL を手動でコピーするのではなく、この方法を推奨します。managePgDatabase(action=repairMigration, migrationVersion=..., migrationName=..., repairStatus=..., repairReason=...)— 履歴レコードを修復します
executeは DML と運用 SQL 用であり、DDL のデフォルト手段ではありません:INSERT/UPDATE/DELETEにはmanagePgDatabase(action=execute, confirm=true)を使用します。また、GRANT/CREATE POLICY/ストレージの RLS が移行に含まれない場合も、同じ方法を使用します。executeでスキーマ DDL を実行しようとすると、allowDdlViaExecute=trueを明示的に設定しない限り、ツールによってDDL_USE_APPLY_MIGRATIONとしてソフトブロックされます(これは回避策としてのみ使用してください)。 🚨 重大:テーブルを作成する前に、テーブルの有無とカラム名を確認してください。 テーブルがすでに存在する場合、CREATE TABLE IF NOT EXISTSはカラム名が間違っていても何もせずに処理をスキップします。まず必ずqueryPgDatabase(action="sql", sql="SELECT column_name, data_type FROM information_schema.columns WHERE table_name='xxx'")を呼び出し、テーブルが存在するか、正確なカラム名が何かを確認してください。テーブルがすでに存在し、カラム名が一致しない場合(例:uidではなくuser_id)、次のいずれかを行う必要があります。applyMigrationで新しいバージョンを指定してALTER TABLEを実行し、カラムを追加・名前変更・削除する。- または、データ損失が許容される場合(使い捨て環境や評価環境など)に限り、
DROP TABLE IF EXISTS ... CASCADEを実行し、applyMigrationで再作成する。 CREATE TABLE IF NOT EXISTSが何もせずに処理をスキップすることに頼らないでください。後続のすべての CRUD クエリで、フィールド名の誤りによるエラーが発生します。- DDL の実行後、スキーマを再度クエリし、フロントエンドコード、挿入/更新ペイロード、フィルター、並べ替え、RLS ポリシーで使用されるすべてのカラム名と照合してください。
-
ログインを実装する前に、ユーザー名とパスワードによる認証を確認してください。
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の挙動を確認してください。ユーザー名による直接のサインアップがサポートされていない場合は、ブラウザーで秘密鍵を公開せず、バックエンド/管理境界を通じて登録を実装してください。
-
CRUD を実装する前に、Web の認証状態を
auth.getSession()で実装してください。- ルートガードでは
auth.getUser()や非推奨のgetLoginState()ではなく、data.sessionを確認してください。 signInWithPassword(...)がerrorを返さず、data.sessionを含む場合にのみ、ログイン成功として扱ってください。author_id/ロール行に使う UID はdata.session.user.idから取得してください(実際のセッションオブジェクトを確認した後に限り、sub/uidを代替として使用してください)。- ログインの証明として
auth.getUser()を使用しないでください。実際のユーザー名/パスワードによるセッションがない場合でも、null 以外のラッパーや匿名ユーザーのようなデータが返ることがあります。
- ルートガードでは
-
ブラウザー側の業務データには、まず CloudBase JS SDK v3 の PostgreSQL API を使用してください:
app.rdb().from(table)。app.rdbがない場合(xxx.rdb is not a functionは SDK が古すぎることを意味します)は、最新の@cloudbase/js-sdkを使用してください。 -
PG の CRUD のために、ブラウザーコードから CloudBase Auth のベアラートークンを手動で取得しないでください。特に、インストール済み SDK にそのメソッドが実際に存在することを確認していない限り、
currentUser.getIdToken()のような標準ではないヘルパーを呼び出さないでください。アクティブなセッションが SDK によって引き継がれるapp.rdb()を優先してください。 -
ポリシーでは、公式の CloudBase PG SQL 認証ヘルパーを使用してください。JWT の
subにはauth.uid()、anon/authenticated/service_roleにはauth.role()、すべてのクレームにはauth.jwt()、メールアドレスが必要な場合にはauth.email()を使います。動作すると断言する前に、実際のアプリセッションでポリシーを検証してください。- 実際のアプリ経路からログインします。
author_id = session.user.idを指定してテスト行を挿入します。queryPgDatabaseでその行を読み戻します。- INSERT/SELECT が失敗した場合は、正確な RLS エラーを調べてポリシーを修正するか、サーバー/RPC 境界に切り替えてください。ブラウザーからアクセスするテーブルで、RLS が壊れたままにしないでください。
- ⚠️
auth.uid()の戻り値はuuidではなくtextです。 所有者カラムには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.*関数を一覧表示してください。
-
PG HTTP API は、OpenAPI ドキュメントを読み、インストール済み SDK の認証モデルを確認した後の代替手段としてのみ使用してください。
/api/v1/rdb/restのような URL を推測しないでください。ドキュメントに記載されているベース URL はhttps://<envId>.api.tcloudbasegateway.com/v1/rdb/rest/<table>であり、認証にはAuthorization: Bearer <Publishable Key | access_token | API Key>を使用します。 -
カバー画像は CloudBase Storage に保存してください。PG に保存するのは、最終的なファイル URL またはファイルメタデータのみです。
-
完了を宣言する前に、プロジェクトのビルド/型チェックと、ログイン/CRUD のブラウザー E2E の両方で確認し、さらに
queryPgDatabaseで行を読み戻してください。RLS をデバッグする際、ツールがロールのシミュレーションに対応している場合は、authenticated/anonとして SQL を実行してください。管理者/デフォルトの実行では、ユーザー向けの失敗が発生しないことがあります。
調査予算
- 幅広い調査よりも、まず動作するユーザーフローを優先してください。
- PG を使った CRUD を行う Web アプリの場合は、任意の HTTP API の詳細を調べる前に
references/app-workflow.mdを読み、記載された完了までの手順に従ってください。 - 同じ質問について、同じドキュメントファミリーを 2 回を超えて検索しないでください。2 回目の検索でも解決しない場合は、インストール済み SDK のインターフェイスか、実際に発生したランタイムエラーを確認してください。
- ブラウザーでの CRUD に
app.rdb()を選んだら、app.rdb()が存在しないか、明らかに失敗する場合を除き、生の PG HTTP API の調査はやめてください。 - DDL が失敗した後に SQL を再試行するのは 2 回までにしてください。その後は
queryPgDatabase(action="objects")を呼び出してスキーマ修飾されたテーブル名を確認し、次にqueryPgDatabase(action="schema", objectName="public.your_table")を実行してください。正確なエラーを確認し、スキーマまたは権限の計画を簡素化してください。 - 対象を絞った修正で、長いタスク管理ループを避けてください。現在有効なファイルを読み、プラットフォームに必要なセットアップを最小限実行し、コードを編集して検証してください。
- ファイル読み取りの上限:同じファイルを2 回を超えて読まないでください。2 回読み取った後に再度読む必要がある場合は、対象を絞った検索には
Grepを使うか、特定の行範囲を指定してReadにoffset/limitを設定してください。再読するのではなく、編集または検証に進んでください。
データモデルのルール
-
ユーザーの情報源として、CloudBase Auth / CloudBase PG の組み込み認証 ID を使用します。アプリで必要でない限り、追加の ID テーブルを複製しないでください。
-
アプリで管理者/編集者の権限が必要な場合は、ビジネス上のロールを 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 を有効にしたブラウザー向けテーブルに、ポリシーが 1 つもない状態で放置しないでください。その状態では PostgreSQL がユーザーからの読み取り/書き込みをデフォルトで拒否するため、UI に汎用的な保存失敗が表示されるだけで、
app.rdb().from("articles").insert(...)が失敗することがあります。RLS を有効にする場合は、アプリをテストする前に SELECT/INSERT/UPDATE/DELETE ポリシーを作成して確認してください。 -
ポリシーでは、CloudBase PG の公式 SQL 認証ヘルパーを使用してください。
auth.uid()(JWT のsub。戻り値はuuidではなくtext)、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はuuidではなくTEXTです。auth.uid()はEchhGXFadSANiCSaVim2wQのような text を返します。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")— これも有効(スキーマのプレフィックスは1つ) - ❌
db.from("public.public.articles")— 誤りです。スキーマのプレフィックスが重複しているため、PGRST205で失敗します queryPgDatabase()のobjectName="public.articles"は MCP ツール形式です。db.from()にコピーしないでください。
静的インポートと、共有の app.rdb() クライアントを1つ使用してください(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 は同じパスプレフィックスを共有し、動詞ごとに1ページあります(URL に .md を追加すると、raw 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 で利用できる API を確認してください:
const { data } = await app.storage.from('covers').upload(`${file.name}`, file);
バケットの存在が必須です(Supabase との同等性)
CloudBase PG ストレージは pgstore バックエンドを使用し、Supabase Storage と同じモデルに従います。すべてのアップロードは、すでに存在するバケットを対象にする必要があります。ブラウザー SDK ではバケットを作成できません。アップロードコードを書く前に、次のことを行ってください。
- 対象のプレフィックス(例:
covers)に使用できる pgstore バケットが存在することを確認します。DescribeEnvs.Storages[]が公開するレガシー NoSQL バケット(例:6d63-…-1409864723)は旧 NoSQL バックエンド用であり、pgstore のアップロードには使用できません。 - 使用できるバケットが存在しない場合は、PG ストレージ管理インターフェース(PG ストレージ HTTP API/CLI/コンソール/適切な場合は
storage.bucketsに対する SQL)から作成します。従来モードのストレージツールや、JS のパスプレフィックスとしてcovers/を追加することで PG バケットが作成されると想定してはいけません。 - バケット名は
from('<bucket>')に指定します。upload(key, file)に渡すキーはそのバケット内のものであり、バケットのプレフィックスを繰り返してはいけません。正しい例:app.storage.from('covers').upload('a.png', file)。誤った例:app.storage.from('covers').upload('covers/a.png', file)。 - バケットを作成した後、
managePgDatabase(action="execute", confirm=true)を使ってstorage.objectsの RLS を設定します。デフォルトの RLS ではすべて拒否されます。許可ポリシーがない場合、ブラウザーはSTORAGE_PERMISSION_DENIEDを受け取ります。バケットと RLS のテンプレート(ユーザーごとの分離とパブリック読み取り用バケット)についてはreferences/storage-pg.mdを、正確な SQL ポリシーについてはcloud-storage-web/SKILL.mdの「Post-bucket: storage RLS」セクションを参照してください。
失敗時の早見表(失敗した POST .../v1/storages/get-objects-upload-info の DevTools ネットワークタブを確認してください):
/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 の「Post-bucket: storage RLS」を参照してください。 |
バケット/キーに対する INVALID_PARAM | SDK/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) } を渡すか、Blob/File を使って app.storage.from('<bucket>').upload('<key>', file) を呼び出し、SDK にヘッダーを設定させます。 |
DevTools に PUT https://undefined/ と net::ERR_NAME_NOT_RESOLVED が表示された場合、これは上記3つのいずれかの行に該当する症状です。上流のメタデータ応答に uploadUrl フィールドがなく、SDK が後続の PUT に undefined を渡しています。必ず最初に上流の get-objects-upload-info 応答を調べてください。https://undefined/ の URL 自体を追跡してはいけません。
厳守事項:アップロードエラーを決して握りつぶしてはいけません。uploadCoverImage() が reject した場合、呼び出し元の createArticle() の処理も必ず reject しなければなりません。捏造したカバー画像 URL を使って PG に挿入したり、成功トーストを表示したり、推測したバケット名で再試行したりしてはいけません。
ExecutePGSql/DDL のトラブルシューティング
ExecutePGSql/managePgDatabase(action="execute")は管理/コントロールプレーンの経路です。Tencent Cloud SecretKey や CloudBase API Key をフロントエンドのコードに公開しないでください。- 1 回の呼び出しにつき、SQL ステートメントは 1 つだけ実行してください。セミコロンで複数のステートメントを連結して送信せず、明示的に分割してください。
- 一部の DDL(
CREATE/ALTER/DROP/GRANT/REVOKE/TRUNCATE/COMMENT)は、一時的なInternalErrorにより直接実行できない場合があります。その場合は、DDL をDO $$ BEGIN EXECUTE '...'; END $$でラップし、文字列内のシングルクォートをエスケープして 1 回だけ再試行してください。 - 権限を検証する際は、ツール/API がロールパラメーターに対応している場合、ユーザー向けのロール(
anonまたはauthenticated)を使用してください。デフォルトの管理者実行では、GRANT/RLS ポリシーの不足が隠れることがあります。
HTTP API のフォールバック
- PG HTTP API は、MySQL と同じ CloudBase リレーショナルデータベース HTTP API ファミリーに属します。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がある場合、それらはベース URL としてのみ扱ってください。パス、メソッド、ヘッダー、認証方式は、引き続き OpenAPI ドキュメントまたは既存の動作するヘルパーから確認する必要があります。
フロントエンドのガードレール
動的ヘルパーに関する落とし穴を避けてください。
function getAuth() { return (await import("./backend")).auth; }のように記述しないでください。トップレベルで静的インポートを使うか、関数をasyncにしてください。- Vite で
typeof import !== "undefined"を記述しないでください。import.meta.envを直接使用してください。 - Vite が変換エラーを報告した後は、編集を続けないでください。まず構文を修正し、ビルドを再実行してから、ブラウザーのフローをテストしてください。
- ドキュメント化された v3 のインターフェースがある場合、無関係な SDK の内部実装をリバースエンジニアリングすることに時間を費やさないでください。まず、ドキュメント化された
app.rdb()/app.storage.from()API を使用してください。
クイックチェック
- PG スキーマが存在し、サービスコードと一致していること。
- ユーザー名ログインが有効で、コードがメール API ではなくユーザー名 API を使用していること。
- データ書き込みが、ローカル状態、モック配列、または推測による 404 エンドポイントではなく、JS SDK v3 の
app.rdb()またはドキュメント化された HTTP API パスを経由して CloudBase PG に到達すること。 - ブラウザーの PG コードが
user.getIdToken()や独自に作成したトークンヘルパーに依存していないこと。生の HTTP が避けられない場合は、まずインストール済みの CloudBase Web SDK/auth API を確認し、現在のユーザーセッションでリクエストが成功することを実証してください。 - エディターの権限が UI の外部で適用されていること。
- アップロードパス(例:
covers)に一致する pgstore バケットが、ブラウザーからアップロードを実行する前に存在すること。存在しない場合は、管理用インターフェースを使って作成してください。v3 SDK が代わりに作成することはありません。 - ストレージのアップロードが使用可能な URL を返し、その URL が記事とともに永続化されること。アップロードエラーは伝播させてください。プレースホルダーのカバー URL を使って記事行を挿入しないでください。
リファレンス一覧
パッケージに含まれるすべてのリファレンスファイル(スキルの到達可能性チェックに必須):