OpenMAIC StorageProvider Interface: A Backend-Agnostic Contract for Asset Persistence
The StorageProvider interface in OpenMAIC defines a minimal, three-method contract—put, resolve, and remove—that abstracts binary asset storage across any implementation, from IndexedDB to cloud CDNs, while maintaining stable references in DSL documents.
The THU-MAIC/OpenMAIC repository decouples document structure from storage mechanics through the StorageProvider interface defined in packages/@openmaic/dsl/src/storage.ts. This abstraction enables the platform to persist images, audio, and video files through diverse backends without embedding storage-specific URLs directly into DSL documents. By pairing this interface with the enumeration capabilities in packages/@openmaic/dsl/src/asset-manifest.ts, OpenMAIC creates a persistence layer where asset references remain stable even as underlying storage implementations change.
StorageProvider Interface Structure
The StorageProvider interface declares three asynchronous methods that form the complete lifecycle of binary asset management. Located at lines 86-93 of packages/@openmaic/dsl/src/storage.ts, this contract requires implementations to handle storage, retrieval, and deletion operations while returning opaque references rather than direct URLs.
The put Method
The put method stores binary data and returns a unique asset reference.
(data: BinaryBlob, meta?: AssetMeta) => Promise<AssetRef>
When invoked, put accepts a BinaryBlob containing the payload bytes and optional AssetMeta metadata, then persists the data to the underlying storage. Every successful call must return a freshly allocated AssetRef string, even if the identical binary content already exists in storage. This guarantees that each stored asset receives a unique identifier that can be embedded safely in DSL documents without collision risks.
The resolve Method
The resolve method translates an opaque reference into a publicly accessible URL.
(ref: AssetRef) => Promise<string | null>
This method takes an AssetRef and returns a promise resolving to a URL string suitable for use as a source in <img>, <audio>, or <video> elements. If the reference does not correspond to stored bytes, the method returns null. The implementation handles backend-specific URL generation, whether generating temporary presigned S3 URLs or resolving IndexedDB object URLs.
The remove Method
The remove method deletes assets from storage.
(ref: AssetRef) => Promise<void>
This operation deletes the binary data associated with the provided reference. When invoked with a non-existent reference, remove executes as a no-op rather than throwing errors. This behavior ensures safe cleanup operations in document editing workflows where references may have already been purged.
Supporting Types
The interface relies on three core type definitions that ensure type safety across the persistence layer.
AssetRef
An AssetRef is a simple opaque string that uniquely identifies stored bytes. Defined as a type alias in packages/@openmaic/dsl/src/storage.ts, this reference is intentionally opaque to callers and embeds directly into DSL documents as a stable identifier, surviving storage backend migrations that would invalidate direct URLs.
BinaryBlob
The BinaryBlob type provides a minimal, DOM-independent representation of binary payloads. It specifies size, MIME type, and an arrayBuffer() method, making it compatible with browser-native Blob objects and Node.js buffers while keeping the DSL package free of DOM dependencies.
AssetMeta
Optional AssetMeta defines structured-cloneable metadata stored alongside the asset, such as contentType. This metadata travels with the binary data through put operations, allowing storage implementations to preserve content-type headers or encryption parameters without polluting the asset reference itself.
Integration with Asset Manifest
The StorageProvider interface operates in concert with packages/@openmaic/dsl/src/asset-manifest.ts to enable enumerable, stable asset management. While asset-manifest.ts defines the manifest structure and enumeration logic, it relies on StorageProvider implementations to materialize assets into accessible URLs.
This integration enables three critical capabilities:
- Stable References: DSL documents store
AssetRefstrings rather than fragile URLs, ensuring documents remain valid when assets migrate between storage backends. - Lazy Resolution: Exporters for formats like ZIP, PPTX, or video files enumerate references via the manifest but only call
resolve()when generating the final output, minimizing unnecessary URL generation. - Atomic Replacement: Documents update atomically by swapping
AssetRefvalues—callingput()for new data, updating the document reference, thenremove()for the old data—without breaking existing links during the transition.
Implementation Examples
OpenMAIC provides concrete implementations in the storage package, including browser-based IndexedDB stores and HTTP backends. You can also implement custom providers by satisfying the three-method contract.
In-Memory Storage Provider
The following implementation demonstrates a simple memory-backed provider that satisfies the StorageProvider interface:
import type { StorageProvider, BinaryBlob, AssetMeta, AssetRef } from '@openmaic/dsl';
class MemoryStorage implements StorageProvider {
private store = new Map<AssetRef, { blob: BinaryBlob; meta?: AssetMeta }>();
private counter = 0;
async put(data: BinaryBlob, meta?: AssetMeta): Promise<AssetRef> {
const ref: AssetRef = `mem-${this.counter++}`;
this.store.set(ref, { blob: data, meta });
return ref;
}
async resolve(ref: AssetRef): Promise<string | null> {
return this.store.has(ref) ? `memory://${ref}` : null;
}
async remove(ref: AssetRef): Promise<void> {
this.store.delete(ref);
}
}
Resolving Assets via the Manifest
When processing documents, use the enumeration function from asset-manifest.ts to collect all references, then resolve them through your provider:
import { enumerateAssetManifest } from '@openmaic/dsl/asset-manifest';
import type { AssetManifestDocument } from '@openmaic/dsl/asset-manifest';
import { StorageProvider } from '@openmaic/dsl';
// Assume `doc` conforms to AssetManifestDocument
const manifest = enumerateAssetManifest(doc);
// Resolve each asset with a concrete provider (e.g., BrowserAssetStore)
async function resolveAll(provider: StorageProvider) {
for (const entry of manifest.entries) {
const url = await provider.resolve(entry.ref);
console.log(`Asset ${entry.ref} (${entry.kind}) → ${url ?? 'missing'}`);
}
}
Swapping Existing Assets
For document editing workflows that replace assets while maintaining document integrity:
async function replaceAsset(
provider: StorageProvider,
oldRef: AssetRef,
newBlob: BinaryBlob,
meta?: AssetMeta
) {
const newRef = await provider.put(newBlob, meta);
// Document update logic would replace `oldRef` with `newRef`
await provider.remove(oldRef);
}
Summary
The StorageProvider interface in OpenMAIC establishes a clean separation between DSL document structure and binary persistence:
- Three-method contract:
put(),resolve(), andremove()handle complete asset lifecycles without exposing backend details. - Opaque references:
AssetRefstrings provide stable identifiers that survive storage migrations, unlike direct URLs. - Backend flexibility: Implementations in
packages/@openmaic/storage/src/asset/browser-store.tsandhttp.tsdemonstrate support for IndexedDB and HTTP backends using identical interfaces. - Manifest integration:
packages/@openmaic/dsl/src/asset-manifest.tsleverages the interface to enumerate and resolve assets lazily during export operations.
Frequently Asked Questions
What is the relationship between StorageProvider and asset-manifest.ts?
While the StorageProvider interface is defined in packages/@openmaic/dsl/src/storage.ts, it serves as the persistence contract for the asset enumeration system in packages/@openmaic/dsl/src/asset-manifest.ts. The manifest tracks which AssetRef values exist within a document, while the provider implementation resolves those references to retrievable URLs and handles actual binary storage.
How does AssetRef differ from a storage URL?
An AssetRef is an opaque string identifier embedded in DSL documents, while the URL returned by resolve() represents a concrete, often temporary, access mechanism. This distinction allows documents to remain valid when storage backends change—only the resolve() implementation updates to generate new URLs, while the AssetRef stored in the document stays constant.
Can StorageProvider implementations cache resolved URLs?
Yes, implementations may cache URL generation results, though they must respect the contract that resolve() returns null for non-existent references. Concrete implementations like BrowserAssetStore in packages/@openmaic/storage/src/asset/browser-store.ts typically generate object URLs or presigned links that may expire, requiring fresh resolution calls for long-lived documents.
What happens if remove() is called on an active reference?
The remove() method executes as a no-op when given a non-existent reference, preventing runtime errors during cleanup operations. However, once an asset is removed, subsequent calls to resolve() with that AssetRef will return null, and any document containing that reference will display missing asset behavior until the reference is updated to point to new data via put().
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →