mcpField
E
extension-object-storage

extension-object-storage

github.com/caffeinelabsクラウド・DevOps228.6k 回インストール1 週間前に更新
リポジトリ

extension-object-storage について

extension-object-storage は、ファイル本体をオフチェーンに保存し、アプリのバックエンドには参照を保持する Caffeine AI 向けスキルです。ExternalBlob によるファイル管理と、フロントエンドでのアップロード、表示、ダウンロードを扱います。

主な機能

  1. 01オフチェーンのファイル保存
  2. 02ExternalBlob によるファイル参照
  3. 03MIME タイプとファイル名を指定したアップロード
  4. 04プロキシ URL によるファイル表示
  5. 05元のファイル名でバイト列をダウンロード

活用シーン

  1. 01ブラウザーから画像や文書をアップロード
  2. 02バックエンドのレコードにファイル参照を保存
  3. 03画像や動画をインライン表示
  4. 04元のファイル名でファイルをダウンロード

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/caffeinelabs/skills --skill extension-object-storage

インストール後、Claude Code で/extension-object-storageを入力して読み込まれたか確認します。

詳細

オブジェクトストレージ

Caffeine AI 用のオブジェクトストレージ拡張機能です。

概要

このスキルは、オンチェーン参照を使ったオフチェーンのファイル/オブジェクトストレージを追加します。MixinObjectStorage ミックスインはファイル操作のための基盤を提供します。アップロードしたファイルは、Storage.ExternalBlob を使って独自のデータ構造で管理します。

必須セットアップチェックリスト

4 つの手順すべてが必須です。いずれか 1 つでも省略すると、アップロード時に 403 Forbidden: Invalid payload が発生します。

  1. mops の依存関係 — [dependencies] の下に caffeineai-object-storage を mops.toml に追加します。
  2. ミックスインの呼び出し — main.mo に include MixinObjectStorage() を記述します("mo:caffeineai-object-storage/Mixin" からインポートします)。
  3. Storage.ExternalBlob 型 — ファイルを表すデータフィールドには、必ず Storage.ExternalBlob を使用します。Text は使用しないでください。
  4. フロントエンドの npm パッケージ — @caffeineai/object-storage をインストールし、呼び出し元で ExternalBlob.fromBytes(bytes, file.type, file.name) を使用します。

重要:フロントエンドパッケージ(@caffeineai/object-storage)は、バックエンドの mops パッケージ(caffeineai-object-storage)なしでは動作しません。npm パッケージのみをインストールし、mops パッケージをインストールしないと、アップロードが失敗してもエラーが表示されません(ストレージゲートウェイから 403 が返されます)。両方を必ず一緒にインストールしてください。

バックエンド

ファイルの内容はオフチェーンに保存されます。バックエンドは、mo:caffeineai-object-storage/Storage の Storage.ExternalBlob 型を使って外部ファイルへの参照を管理します。実際のアップロード/ダウンロードはフロントエンドが処理し、バックエンドは参照のみを保存します。

重要:ファイル、画像、写真、ドキュメント、メディアを表すデータフィールドには、型として必ず Storage.ExternalBlob を使用してください。Text は絶対に使用しないでください。Text を使うと、アップロード/ダウンロードのプロキシが動作しなくなります。ファイルのアップロードを受け付けるメソッドのパラメーターにも、Text ではなく Storage.ExternalBlob を使用してください。

正しい例:

blob : Storage.ExternalBlob

誤った例:

blobId : Text
imageUrl : Text
fileRef : Text

モジュール API

mo:caffeineai-object-storage/Storage から使用する型は ExternalBlob(Blob です)だけです。Storage.mo の他の関数はすべて MixinObjectStorage が使用する内部基盤です。直接呼び出さないでください。

main.mo でのセットアップ

include MixinObjectStorage() は、カスタムのミックスインファイルではなく、必ず main.mo に記述してください。独自のファイル追跡ロジックは、別のミックスインに記述します。

import MixinObjectStorage "mo:caffeineai-object-storage/Mixin";
import Storage "mo:caffeineai-object-storage/Storage";

actor {
  include MixinObjectStorage();

  // Track file references
  type Data = {
    id : Text;
    blob : Storage.ExternalBlob;
    name : Text;
    // other metadata
  };
};

誤り:ストレージメソッドを独自に実装しないでください

_immutableObjectStorageCreateCertificate やその他の _immutableObjectStorage* メソッドを独自に実装しないでください。これらはプラットフォーム専用の予約済みメソッド名であり、mops パッケージの MixinObjectStorage ミックスインが提供するものです。手書きの実装では戻り値の型が誤り、アップロード時に 403 Forbidden: Invalid payload が発生します。

誤り — main.mo にインラインスタブを記述する場合:

// WRONG: Do not write this yourself
public shared func _immutableObjectStorageCreateCertificate(fileHash : Text) : async Blob {
  CertifiedData.set(Blob.fromArray(hashBytes));
  Blob.fromArray([])
};

誤り — プラットフォームの形式を模倣したカスタムミックスインファイル:

// WRONG: Do not create src/backend/mixins/object-storage-api.mo
import ObjectStorageMixin "mixins/object-storage-api";
include ObjectStorageMixin();

正しいインポートパスは必ず "mo:caffeineai-object-storage/Mixin" です。これは mops パッケージであり、相対パスではありません。"mixins/object-storage-api" や "./ObjectStorage" のような相対インポートはすべて誤りです。

プラットフォームのミックスインが生成する正しいシグネチャは次のとおりです:

_immutableObjectStorageCreateCertificate : (blobHash : Text) -> async record { method : Text; blob_hash : Text }

その他の戻り値の型(Blob、(), Text など)では、ゲートウェイの検証に失敗します。

フロントエンド

バックエンドの Blob フィールドは、フロントエンドでは ExternalBlob として表されます。

import { ExternalBlob } from "@caffeineai/object-storage";
import type { FileRecord } from "@caffeineai/object-storage";

ExternalBlob API

class ExternalBlob {
  getBytes(): Promise<Uint8Array<ArrayBuffer>>;
  getDirectURL(): string;
  static fromURL(url: string): ExternalBlob;
  static fromBytes(
    blob: Uint8Array<ArrayBuffer>,
    contentType?: string,
    filename?: string,
  ): ExternalBlob;
  withUploadProgress(onProgress: (percentage: number) => void): ExternalBlob;
}

ファイルのアップロード

ゲートウェイの Blob ツリーに Content-Type と Content-Disposition(元のファイル名)が保存されるように、ブラウザーの File 型と名前を fromBytes に渡します。また、アプリのレコードにリストや UI 用のファイル名を保持できるように、file.name もバックエンドに渡します。

const handleUpload = async (file: File) => {
  const bytes = new Uint8Array(await file.arrayBuffer());
  const blob = ExternalBlob.fromBytes(bytes, file.type, file.name).withUploadProgress((pct) => {
    setProgress(pct);
  });

  await actor.uploadFile(file.name, blob);
};

ゲートウェイからの GET/HEAD レスポンスでは、保存されたファイル名が Content-Disposition を介して返されます。ゲートウェイにアクセスせずに検索や表示ができるよう、バックエンドの filename フィールドも保持してください。

ファイルの表示

インライン表示(画像、動画)には getDirectURL() を使用します。これは不透明なプロキシ URL を返します。ファイル拡張子がないため、ファイルの種類を判定する目的で URL を調べないでください。

<img src={record.blob.getDirectURL()} alt={record.filename} />

ファイルタイプの検出

重要:getDirectURL() の URL を調べてファイルタイプを検出しないでください。これらは拡張子のない不透明なプロキシ URL です。代わりに、バックエンドレコードの filename フィールドを使用してください。

const isImage = (filename: string) =>
  /\.(jpg|jpeg|png|gif|webp|svg|bmp|ico)$/i.test(filename);

// Conditional rendering
{isImage(record.filename) ? (
  <img src={record.blob.getDirectURL()} alt={record.filename} />
) : (
  <div>{record.filename}</div>
)}

バックエンドから mimeType フィールドも返される場合は、そちらを優先してください。

const isImage = (mimeType?: string) => mimeType?.startsWith("image/");

ファイルのダウンロード

元のファイル名でダウンロードするには、getBytes() を使ってダウンロードリンクを作成してください。

const handleDownload = async (record: FileRecord) => {
  const bytes = await record.blob.getBytes();
  const blob = new Blob([bytes]);
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = record.filename;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
};

インライン表示には getDirectURL() を、名前を付けて保存するダウンロードには getBytes() を使用してください。

概要

用途メソッド備考
画像/動画の表示blob.getDirectURL()ストリーミング、キャッシュ済み
ファイル名を指定したダウンロードblob.getBytes()Blob とアンカーでラップ
ブラウザーからのアップロードExternalBlob.fromBytes(bytes, file.type, file.name)ゲートウェイヘッダーに MIME タイプとファイル名を含めます
ファイルタイプの検出filename または mimeType フィールドURL は絶対に調べないでください

セットアップの確認

バックエンドに mops 依存関係がインストールされていることを確認してください。src/backend/mops.toml を確認します。

[dependencies]
caffeineai-object-storage = "0.1.2"

[dependencies] に caffeineai-object-storage がない場合、フロントエンド側の対応にかかわらず、オブジェクトストレージは動作しません。これを追加し、mops install を実行してから、再ビルドしてください。

トラブルシューティング

エラー原因対処方法
PUT /v1/blob-tree/ で 403 Forbidden: Invalid payloadバックエンドのカニスターに _immutableObjectStorageCreateCertificate がない、または誤った型を返しているmops.toml に caffeineai-object-storage をインストールし、main.mo に include MixinObjectStorage() を追加して、再デプロイします
403 Forbidden: Invalid payload(すべてのファイル)@caffeineai/object-storage npm はインストールされているが、caffeineai-object-storage mops はインストールされていないmops 依存関係を追加し、バックエンドを再ビルドします
メソッドは存在するが、依然として 403 が発生する手書きのスタブが誤った型を返している(例:record { method; blob_hash } ではなく Blob または () を返している)カスタム実装を削除し、代わりにプラットフォームのミックスインを使用します
Forbidden: Owner does not have an account with the cashierCashier の登録に関する問題(このスキルとは無関係)自動修復による登録を実行するため、バックエンドのカニスターを再デプロイします

コメント