@ooneex/storage component is a multi-backend file storage layer. Every backend implements the same IStorage interface — put, getFile, getAsJson, delete, list, and more — so you can start on the local filesystem and move to Cloudflare R2 or Bunny without changing a single call site. Files are organized into buckets, and the S3-compatible backends share one abstract Storage base class.
Why this component
- One interface, many backends. Filesystem, Cloudflare R2, and Bunny all implement
IStorage— swap backends without touching callers. - Bucket-based. Group files under a bucket with
setBucket()/getBucket(); list or clear a whole bucket in one call. - Flexible content.
put()accepts strings,ArrayBuffer,Blob,Request/Response, andBunFile/S3File. - Read it your way. Pull a file back as JSON, an
ArrayBuffer, a stream, or write it straight to disk withgetFile(). - Directory uploads.
putDir()walks a local directory recursively with an optional regex filter. - Container-managed. Register a storage class with a decorator and resolve it from the container.
How it works
You pick (or implement) a backend and register it. Reads and writes go through theIStorage methods; the backend handles the transport, bucketing, and content conversion.
| Method | Purpose |
|---|---|
getBucket() | Return the active bucket name. |
setBucket(name) | Switch the active bucket; returns the storage for chaining. |
list() | List the keys in the current bucket. |
clearBucket() | Delete every object in the current bucket. |
exists(key) | Whether an object exists. |
delete(key) | Remove one object. |
put(key, content) | Write content; returns the number of bytes written. |
putFile(key, localPath) | Upload a local file. |
putDir(bucket, options) | Upload a local directory recursively with an optional filter. |
getFile(key, options) | Download an object to options.outputDir (optionally renamed via options.filename). |
getAsJson<T>(key) | Parse the object as JSON. |
getAsArrayBuffer(key) | Read the object as an ArrayBuffer. |
getAsStream(key) | Read the object as a ReadableStream. |
| Backend | Best for | Transport |
|---|---|---|
FilesystemStorage | Local development, single-instance storage | Local disk |
CloudflareStorage | Production object storage on Cloudflare R2 | S3-compatible (HTTPS) |
BunnyStorage | Edge file delivery via Bunny Storage zones | Bunny Storage SDK (HTTPS) |
FilesystemStorage and CloudflareStorage extend the shared Storage base class and talk to an S3 client under the hood. BunnyStorage implements IStorage directly against the Bunny Storage SDK.
Environment variables
| Variable | Backend | Required | Purpose |
|---|---|---|---|
FILESYSTEM_STORAGE_PATH | FilesystemStorage | Yes | Base directory for stored files. Missing throws StorageException (STORAGE_ROOT_DIR_REQUIRED). |
STORAGE_CLOUDFLARE_ACCESS_KEY | CloudflareStorage | Yes | R2 access key. Missing throws StorageException (CONFIG_REQUIRED). |
STORAGE_CLOUDFLARE_SECRET_KEY | CloudflareStorage | Yes | R2 secret key. Missing throws StorageException (CONFIG_REQUIRED). |
STORAGE_CLOUDFLARE_ENDPOINT | CloudflareStorage | Yes | R2 S3 endpoint URL. Missing throws StorageException (CONFIG_REQUIRED). |
STORAGE_CLOUDFLARE_REGION | CloudflareStorage | No | R2 region. Defaults to EEUR. |
STORAGE_BUNNY_ACCESS_KEY | BunnyStorage | Yes | Bunny storage access key. Missing throws StorageException (API_KEY_REQUIRED). |
STORAGE_BUNNY_STORAGE_ZONE | BunnyStorage | Yes | Bunny storage zone name. Missing throws StorageException (STORAGE_ZONE_REQUIRED). |
STORAGE_BUNNY_REGION | BunnyStorage | No | Bunny region code (de, uk, ny, la, sg, se, br, jh, syd). Defaults to de. |
Usage
The API is the same regardless of backend.Decorator and usage
@decorator.storage()
Registers a storage class with the container. It accepts an optional scope (defaults to singleton). Use it to register a built-in backend under your own name, or a custom backend that extends Storage or implements IStorage.
Exceptions
The component throwsStorageException when a backend is misconfigured or an operation fails. It carries a machine-readable key, a human-readable message, and a data object.
| Key | When |
|---|---|
STORAGE_ROOT_DIR_REQUIRED | FilesystemStorage is created without FILESYSTEM_STORAGE_PATH. |
CONFIG_REQUIRED | CloudflareStorage is missing its access key, secret key, or endpoint. |
API_KEY_REQUIRED | BunnyStorage is missing its access key. |
STORAGE_ZONE_REQUIRED | BunnyStorage is missing its storage zone. |
STORAGE_BUCKET_REQUIRED | A FilesystemStorage operation runs before setBucket(). |
STORAGE_UPLOAD_FAILED | A write or directory creation failed. |
STORAGE_DOWNLOAD_FAILED | A read or download failed. |
STORAGE_LIST_FAILED | Listing a bucket failed. |
STORAGE_DELETE_FAILED | Deleting an object failed. |
STORAGE_CLEAR_FAILED | Clearing a bucket failed. |
FILE_NOT_FOUND | A read targeted an object that does not exist. |
UNSUPPORTED_CONTENT_TYPE | BunnyStorage.put() received content it cannot convert. |
Best practices
- Set the bucket first. Call
setBucket()before reads or writes —FilesystemStoragethrowsSTORAGE_BUCKET_REQUIREDotherwise. - Use stable, namespaced keys. Group related objects under prefixes like
avatars/123.pngsolist()andclearBucket()stay predictable. - Stream large files. Prefer
getAsStream()(andputDir/putFilefor local sources) over loading whole files into memory. - Filter directory uploads. Pass a
filterregex toputDir()so you only ship the files you intend to. - Pick the backend per environment. Filesystem for local/dev, Cloudflare R2 or Bunny for production — the code stays identical.
- Handle the missing case. Reads throw
FILE_NOT_FOUND(orSTORAGE_DOWNLOAD_FAILED) when an object is absent; catchStorageExceptionand branch onerror.key. - Throw
StorageExceptionwith a stablekey. In custom backends, keep keys constant and put variable detail indata.
CLI command
Scaffold a storage adapter and its test file with the generator. It writes the class undermodules/<module>/src/storage/<Name>Storage.ts and installs @ooneex/storage if it is missing.
| Option | Description | Default |
|---|---|---|
--name | Storage class name. The Storage suffix is appended automatically. | Prompted if omitted |
--module | Target module the class is generated into. | shared |
--override | Overwrite an existing class without prompting. | false |
Storage as an S3-compatible adapter, ready for you to set the bucket and confirm its credentials:
Use with Claude and Codex
The generator ships a matchingstorage:create skill. It runs the scaffold and then guides your AI agent through completing the adapter — setting the bucket, wiring the credential env vars, and finishing the test file. Initialize the skills once for your agent:
- Claude
- Codex
Prompt
storage:create --name=Upload, then sets the bucket and credential env vars on the generated Storage subclass.