mcpField
P
postgresql-development-cloudbase

postgresql-development-cloudbase

github.com/tencentcloudbaseデータ・データベース7979 回インストール1 日前に更新
リポジトリ

postgresql-development-cloudbase について

postgresql-development-cloudbaseは、CloudBase PostgreSQLアプリ開発を案内するスキルです。CloudBase PGでWebアプリやCMSを構築する開発者向けに、環境確認からPG専用API、マイグレーション、権限、RLSによるデータアクセスまでを扱います。

主な機能

  1. 01環境でPostgreSQLが有効か確認
  2. 02PGとNoSQLのAPIを区別
  3. 03マイグレーションでスキーマ管理
  4. 04権限と行レベルセキュリティの設定
  5. 05フロントエンドのCRUDとストレージ実装を案内

活用シーン

  1. 01Webアプリ向けCloudBase PGの設定
  2. 02マイグレーションによるテーブル作成・変更
  3. 03authenticatedユーザーのデータアクセス設定
  4. 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.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 プラグイン一式(または不足しているスキル)のインストールをユーザーに依頼してください。リモートのスキルやプロトコルの 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
  • コンソール経由:環境を作成
  1. まず既存のアプリの各所を確認します。src/lib/backend.*、src/lib/auth.*、src/lib/*service.*、ルートガード、既存のフォームに紐付けられたハンドラーを調べてください。

  2. MCP を通じて PG の状態を確認します。スキーマの確認や読み取り専用の調査には queryPgDatabase を使い、DDL/DML には managePgDatabase を使ってください。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 にはスネークケース(例: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 より古いバージョンを適用する必要がある場合(ブランチのマージ/チェリーピック)は、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 ポリシーで使用されるすべてのカラム名と照合してください。
  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 を実装する前に、Web の認証状態を auth.getSession() で実装してください。

    • ルートガードでは auth.getUser() や非推奨の getLoginState() ではなく、data.session を確認してください。
    • signInWithPassword(...) が error を返さず、data.session を含む場合にのみ、ログイン成功として扱ってください。
    • author_id/ロール行に使う UID は data.session.user.id から取得してください(実際のセッションオブジェクトを確認した後に限り、sub/uid を代替として使用してください)。
    • ログインの証明として auth.getUser() を使用しないでください。実際のユーザー名/パスワードによるセッションがない場合でも、null 以外のラッパーや匿名ユーザーのようなデータが返ることがあります。
  7. ブラウザー側の業務データには、まず CloudBase JS SDK v3 の PostgreSQL API を使用してください:app.rdb().from(table)。app.rdb がない場合(xxx.rdb is not a function は SDK が古すぎることを意味します)は、最新の @cloudbase/js-sdk を使用してください。

  8. PG の CRUD のために、ブラウザーコードから CloudBase Auth のベアラートークンを手動で取得しないでください。特に、インストール済み SDK にそのメソッドが実際に存在することを確認していない限り、currentUser.getIdToken() のような標準ではないヘルパーを呼び出さないでください。アクティブなセッションが SDK によって引き継がれる app.rdb() を優先してください。

  9. ポリシーでは、公式の 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.* 関数を一覧表示してください。
  10. 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> を使用します。

  11. カバー画像は CloudBase Storage に保存してください。PG に保存するのは、最終的なファイル URL またはファイルメタデータのみです。

  12. 完了を宣言する前に、プロジェクトのビルド/型チェックと、ログイン/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 ではバケットを作成できません。アップロードコードを書く前に、次のことを行ってください。

  1. 対象のプレフィックス(例:covers)に使用できる pgstore バケットが存在することを確認します。DescribeEnvs.Storages[] が公開するレガシー NoSQL バケット(例:6d63-…-1409864723)は旧 NoSQL バックエンド用であり、pgstore のアップロードには使用できません。
  2. 使用できるバケットが存在しない場合は、PG ストレージ管理インターフェース(PG ストレージ HTTP API/CLI/コンソール/適切な場合は storage.buckets に対する SQL)から作成します。従来モードのストレージツールや、JS のパスプレフィックスとして covers/ を追加することで 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 の「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_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) } を渡すか、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 を使って記事行を挿入しないでください。

リファレンス一覧

パッケージに含まれるすべてのリファレンスファイル(スキルの到達可能性チェックに必須):

コメント