首页/云计算与基础设施/extension-object-storage
E

extension-object-storage

by @caffeinelabsv
4.6(120)

此技能提供链下文件和对象存储能力,专为处理图片、视频、文档等大容量数据设计。它支持超越区块链限制的大文件存储,并通过浏览器缓存的HTTP URL实现高效访问,极大提升了数据密集型应用的性能和用户体验,是构建媒体库和文件管理系统的理想选择。

object-storagefile-managementoff-chain-storagedata-storagelarge-filesGitHub
安装方式
npx skills add https://github.com/caffeinelabs/skills --skill extension-object-storage
compare_arrows

Before / After 效果对比

1 组
使用前

开发者需要手动设计和实现链下大文件存储方案,包括上传、下载、引用管理和缓存优化,耗费大量时间和精力。

使用后

此技能提供标准化的链下对象存储能力,通过简单的集成即可处理大文件,极大简化了开发流程,显著提升效率。

SKILL.md

Object Storage

Object storage extension for Caffeine AI.

Overview

This skill adds off-chain file/object storage with on-chain references. The MixinObjectStorage mixin provides infrastructure for file operations; you track uploaded files in your own data structures using Storage.ExternalBlob.

Required Setup Checklist

All four steps are mandatory. Skipping any one causes 403 Forbidden: Invalid payload at upload time.

  1. mops dependency — add caffeineai-object-storage to mops.toml under [dependencies].
  2. Mixin invocation — include MixinObjectStorage() in main.mo (imported from "mo:caffeineai-object-storage/Mixin").
  3. Storage.ExternalBlob types — every data field that represents a file MUST use Storage.ExternalBlob, never Text.
  4. Frontend npm package — @caffeineai/object-storage installed and ExternalBlob.fromBytes() used at the call site.

CRITICAL: The frontend package (@caffeineai/object-storage) does NOT work without the backend mops package (caffeineai-object-storage). Installing only the npm package and not the mops package causes silent upload failures (403 from the storage gateway). You MUST install both together.

Backend

File content is stored off-chain. The backend manages references to external files using the Storage.ExternalBlob type from mo:caffeineai-object-storage/Storage. The frontend handles the actual upload/download; the backend only stores the reference.

CRITICAL: ANY data field that represents a file, image, photo, document, or media MUST use Storage.ExternalBlob as its type -- NEVER Text. Using Text breaks the upload/download proxy. Method parameters that accept file uploads MUST also use Storage.ExternalBlob, not Text.

Correct:

blob : Storage.ExternalBlob

Wrong:

blobId : Text
imageUrl : Text
fileRef : Text

Module API

The only type you use from mo:caffeineai-object-storage/Storage is ExternalBlob (which is Blob). All other functions in Storage.mo are internal infrastructure used by MixinObjectStorage -- do not call them directly.

Setup in main.mo

include MixinObjectStorage() MUST be placed in main.mo, not in a custom mixin file. Your own file-tracking logic goes in a separate mixin.

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
    };
};

Wrong: Do NOT Implement Storage Methods Yourself

NEVER create your own implementation of _immutableObjectStorageCreateCertificate or any other _immutableObjectStorage* method. These are platform-reserved method names provided exclusively by the MixinObjectStorage mixin from the mops package. Hand-written implementations produce wrong return types and cause 403 Forbidden: Invalid payload at upload time.

Wrong — inline stub in main.mo:

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

Wrong — custom mixin file mimicking the platform shape:

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

The correct import path is ALWAYS "mo:caffeineai-object-storage/Mixin" — a mops package, never a relative path. Any relative import like "mixins/object-storage-api" or "./ObjectStorage" is wrong.

The correct signature produced by the platform mixin is:

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

Any other return type (Blob, (), Text, etc.) will fail gateway validation.

Frontend

Backend Blob fields are represented as ExternalBlob on the frontend.

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>): ExternalBlob;
  withUploadProgress(onProgress: (percentage: number) => void): ExternalBlob;
}

Uploading Files

Convert the browser File object to ExternalBlob and pass the original filename alongside:

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

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

Always send file.name so the backend stores the original filename.

Displaying Files

Use getDirectURL() for inline display (images, videos). This returns an opaque proxy URL -- it has no file extension, so never inspect the URL to determine file type.

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

File Type Detection

CRITICAL: Never detect file types by inspecting the URL from getDirectURL(). These are opaque proxy URLs with no extension. Instead use the filename field from the backend record:

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>
)}

If the backend also returns a mimeType field, prefer that:

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

Downloading Files

For downloads with the original filename, use getBytes() to create a downloadable link:

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);
};

Use getDirectURL() for inline display, getBytes() for save-as downloads.

Summary

Use caseMethodNotes
Display image/videoblob.getDirectURL()Streaming, cached
Download with filenameblob.getBytes()Wrap in Blob + anchor
Upload from browserExternalBlob.fromBytes(bytes)Pair with .withUploadProgress()
Detect file typefilename or mimeType fieldNEVER inspect the URL

Verifying the Setup

Confirm the backend has the mops dependency installed. Check src/backend/mops.toml:

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

If caffeineai-object-storage is missing from [dependencies], object storage will not work regardless of what the frontend does. Add it, run mops install, and rebuild.

Troubleshooting

ErrorCauseFix
403 Forbidden: Invalid payload on PUT /v1/blob-tree/Backend canister missing _immutableObjectStorageCreateCertificate or returning wrong typeInstall caffeineai-object-storage in mops.toml, add include MixinObjectStorage() in main.mo, redeploy
403 Forbidden: Invalid payload (all files)@caffeineai/object-storage npm installed but caffeineai-object-storage mops NOT installedAdd the mops dependency and rebuild backend
Method exists but still 403Hand-written stub returns wrong type (e.g. Blob or () instead of record { method; blob_hash })Remove the custom implementation, use the platform mixin instead
Forbidden: Owner does not have an account with the cashierCashier registration issue (unrelated to this skill)Redeploy the backend canister to trigger self-healing registration

用户评价 (0)

发表评价

效果
易用性
文档
兼容性

暂无评价

统计数据

安装量27.5K
评分4.6 / 5.0
版本
更新日期2026年10月23日
对比案例1 组

用户评分

4.6(120)
5
37%
4
43%
3
13%
2
5%
1
2%

为此 Skill 评分

0.0

兼容平台

🤖claude-code

时间线

创建2026年6月25日
最后更新2026年10月23日
🎁 Agent 知识卡片
调研问卷