extension-object-storage について
extension-object-storage は、ファイル本体をオフチェーンに保存し、アプリのバックエンドには参照を保持する Caffeine AI 向けスキルです。ExternalBlob によるファイル管理と、フロントエンドでのアップロード、表示、ダウンロードを扱います。
主な機能
- 01オフチェーンのファイル保存
- 02ExternalBlob によるファイル参照
- 03MIME タイプとファイル名を指定したアップロード
- 04プロキシ URL によるファイル表示
- 05元のファイル名でバイト列をダウンロード
活用シーン
- 01ブラウザーから画像や文書をアップロード
- 02バックエンドのレコードにファイル参照を保存
- 03画像や動画をインライン表示
- 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 が発生します。
- mops の依存関係 —
[dependencies]の下にcaffeineai-object-storageをmops.tomlに追加します。 - ミックスインの呼び出し —
main.moにinclude MixinObjectStorage()を記述します("mo:caffeineai-object-storage/Mixin"からインポートします)。 - Storage.ExternalBlob 型 — ファイルを表すデータフィールドには、必ず
Storage.ExternalBlobを使用します。Textは使用しないでください。 - フロントエンドの 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 cashier | Cashier の登録に関する問題(このスキルとは無関係) | 自動修復による登録を実行するため、バックエンドのカニスターを再デプロイします |