How OpenMAIC Handles Asset Management in Client-Side Storage
OpenMAIC stores all user-generated assets in a client-side IndexedDB database managed through TypeScript utility modules under lib/persistence, implementing a three-tier architecture of blob storage, metadata tracking, and grace-based eviction.
The THU-MAIC/OpenMAIC repository implements a robust asset management system designed for offline-capable media workflows. Rather than relying solely on remote servers, the codebase persists images, audio, video, and PDFs locally using a Dexie-backed IndexedDB layer. This architecture ensures low-latency access while preventing unbounded storage growth through automated cleanup policies.
Architecture of the Asset Storage Layer
The storage system relies on three tightly-coupled components that separate raw binary data from lifecycle metadata.
The Binary Blob Store
Located in [lib/persistence/asset-byte-store.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/persistence/asset-byte-store.ts), this module manages the assetBytes table—a Dexie-backed IndexedDB store that maps generated UUIDs to JavaScript Blob objects. The module exposes four primary operations:
storeBlob(blob): Writes raw bytes to IndexedDB and returns a unique asset ID.readAssetBlob(assetId): Retrieves the blob and returns a temporary object URL.deleteAssetBlob(assetId): Immediately purges the binary data.clearAllAssets(): Wipes the entire blob store during workspace resets.
The Metadata Collector
The [lib/persistence/asset-collector-schedule.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/persistence/asset-collector-schedule.ts) module tracks relationships between assets and stages (scenes within the workspace). When an asset is created, the scheduleAsset({ assetId, stageId }) function records the association and timestamps the entry. This registry determines when an asset becomes orphaned and eligible for cleanup.
Grace Period and Eviction Logic
[lib/persistence/asset-collection-grace.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/persistence/asset-collection-grace.ts) implements the safety mechanism that prevents premature deletion. Assets marked as orphaned remain in IndexedDB for a configurable grace period (default approximately five minutes) after their originating stage is destroyed. This buffer allows for undo operations and prevents data loss during rapid workspace modifications.
Asset Lifecycle Workflow
Understanding the complete lifecycle clarifies how these components interact during runtime.
- Creation: Components such as the whiteboard or video exporter call
storeBlob()to persist a file. This generates a UUID and writes the blob to theassetBytestable. - Registration: Immediately after storage, the component invokes
scheduleAsset()to link the asset ID to its current stage ID in the metadata collector. - Retrieval: UI components request assets via
readAssetBlob(), which creates an object URL viaURL.createObjectURL()for immediate rendering. - Cleanup: When a stage is deleted, the collector marks associated assets as orphaned. After the grace period expires,
asset-collection-grace.tstriggersdeleteAssetBlob()to free IndexedDB space. - Fallback: If
readAssetBlob()returns null (indicating corruption or eviction), the application falls back to the original remote URL, ensuring continuous UI functionality.
Working with the Storage API
Developers can interact with the asset management layer through the public utility functions exposed in the persistence modules.
Storing a New Asset
When handling file uploads or canvas exports, persist the binary data and register its metadata:
import { storeBlob } from '@/lib/persistence/asset-byte-store';
import { scheduleAsset } from '@/lib/persistence/asset-collector-schedule';
async function persistImage(file: Blob, stageId: string): Promise<string> {
const assetId = await storeBlob(file); // Writes to IndexedDB
await scheduleAsset({ assetId, stageId }); // Registers relationship
return assetId;
}
Retrieving Assets for Display
Convert stored blobs back into displayable URLs:
import { readAssetBlob } from '@/lib/persistence/asset-byte-store';
async function getImageUrl(assetId: string): Promise<string> {
const blob = await readAssetBlob(assetId);
// Handle eviction gracefully
return blob ? URL.createObjectURL(blob) : '';
}
Manual Eviction
While automatic cleanup handles most scenarios, explicit deletion is available:
import { deleteAssetBlob } from '@/lib/persistence/asset-byte-store';
async function purgeAsset(assetId: string): Promise<void> {
await deleteAssetBlob(assetId); // Removes raw bytes from IndexedDB
}
Resetting Workspace Storage
To completely clear the database and prevent re-initialization on next load:
import { clearAllAssets } from '@/lib/persistence/asset-byte-store';
import { LOCALSTORAGE_KEY_DISCARDED_DB } from '@/configs/storage';
await clearAllAssets();
localStorage.setItem(LOCALSTORAGE_KEY_DISCARDED_DB, 'true');
The MAIC_DISCARDED_DB flag in [configs/storage.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/configs/storage.ts) short-circuits the IndexedDB bootstrap sequence during the next session initialization.
Key Implementation Files
| File | Purpose |
|---|---|
[configs/storage.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/configs/storage.ts) |
Declares LOCALSTORAGE_KEY_DISCARDED_DB for reset state management. |
[lib/persistence/asset-byte-store.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/persistence/asset-byte-store.ts) |
Core IndexedDB wrapper for binary blob CRUD operations. |
[lib/persistence/asset-collector-schedule.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/persistence/asset-collector-schedule.ts) |
Tracks asset-to-stage relationships and cleanup eligibility. |
[lib/persistence/asset-collection-grace.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/persistence/asset-collection-grace.ts) |
Implements grace-period logic before eviction. |
[lib/persistence/bootstrap.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/persistence/bootstrap.ts) |
Initializes the IndexedDB schema and wires collectors to the workspace lifecycle. |
Summary
- Client-side persistence: OpenMAIC uses Dexie-backed IndexedDB in
lib/persistenceto store binary assets locally, eliminating network latency for media-heavy workflows. - Three-tier architecture: The system separates blob storage (
asset-byte-store.ts), metadata tracking (asset-collector-schedule.ts), and eviction logic (asset-collection-grace.ts) to maintain data integrity. - Grace-based cleanup: Assets survive for approximately five minutes after their stage is deleted, preventing accidental data loss while controlling storage growth.
- Resilient retrieval: The storage layer falls back to remote URLs when local assets are unavailable, ensuring UI stability.
- Explicit API: Functions like
storeBlob(),readAssetBlob(), andscheduleAsset()provide type-safe interfaces for components while abstracting IndexedDB complexity.
Frequently Asked Questions
What database technology does OpenMAIC use for asset management?
OpenMAIC uses IndexedDB wrapped by the Dexie library for client-side storage. The [lib/persistence/asset-byte-store.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/persistence/asset-byte-store.ts) module manages the assetBytes table, which stores raw binary data as JavaScript Blob objects mapped to UUID keys.
How does OpenMAIC prevent the browser storage from growing indefinitely?
The codebase implements a grace-period eviction strategy. When a stage (scene) is deleted, its associated assets enter an orphaned state but remain in IndexedDB for a configurable timeout (default ~5 minutes). The [asset-collection-grace.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/persistence/asset-collection-grace.ts) module monitors these timers and triggers deleteAssetBlob() only after the safety window expires, balancing space efficiency with undo capabilities.
What happens if an asset is missing from IndexedDB?
The storage layer implements a fallback mechanism. If readAssetBlob() returns null—indicating the asset was evicted, corrupted, or never stored—the application transparently falls back to the original remote URL. This ensures that media elements continue to display even when the local database is cleared or unavailable.
How do I completely reset the asset storage in OpenMAIC?
Invoke clearAllAssets() from [asset-byte-store.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/persistence/asset-byte-store.ts) to wipe IndexedDB, then set the MAIC_DISCARDED_DB flag in localStorage using the LOCALSTORAGE_KEY_DISCARDED_DB constant from [configs/storage.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/configs/storage.ts). This flag prevents the [bootstrap.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/persistence/bootstrap.ts) module from re-initializing the database during the next application load, effectively treating the session as a fresh installation.
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 →