@woven-canvas/asset-sync
v1.0.8
Published
Asset management for WovenCanvas - upload queueing, caching, and URL resolution
Downloads
782
Readme
@woven-canvas/asset-sync
Asset management for local-first applications.
Installation
npm install @woven-canvas/asset-syncUsage
import { AssetManager, LocalAssetProvider } from "@woven-canvas/asset-sync";
// Create a local asset provider (stores in IndexedDB)
const provider = new LocalAssetProvider();
// Create the asset manager
const assetManager = new AssetManager({
provider,
documentId: "my-document-id",
});
// Initialize (opens IndexedDB stores)
await assetManager.init();
// Resume any pending uploads from previous sessions
await assetManager.resumePendingUploads();
// Upload an asset
const blob = new Blob(["..."], { type: "image/png" });
const identifier = crypto.randomUUID();
await assetManager.upload(identifier, blob, {
filename: "image.png",
mimeType: "image/png",
});
// Get display URL (returns blob URL for pending uploads, resolved URL otherwise).
// Pass the device-pixel size the asset will render at so resizing providers can
// return a matching variant.
const url = await assetManager.getDisplayUrl(identifier, {
width: 512,
height: 512,
});Features
- Local-First: Assets stored locally in IndexedDB, synced to cloud when online
- Upload Queueing: Persistent upload queue with automatic retry and exponential backoff
- Blob URLs: Immediate display of pending uploads via blob URLs
- Session Persistence: Resume pending uploads across browser sessions
- Pluggable Providers: Implement custom storage backends
API
AssetManager
The main class for managing assets:
interface AssetManagerOptions {
provider: AssetProvider;
documentId: string;
}
class AssetManager {
constructor(options: AssetManagerOptions);
// Initialize IndexedDB stores
init(): Promise<void>;
// Resume pending uploads from previous sessions
resumePendingUploads(): Promise<void>;
// Upload an asset (returns when upload completes)
upload(
identifier: string,
blob: Blob,
metadata?: AssetMetadata,
): Promise<void>;
// Get URL for display (blob URL for pending, resolved URL otherwise).
// `dimensions` is the device-pixel render size, forwarded to the provider.
getDisplayUrl(
identifier: string,
dimensions: ResolveDimensions,
): Promise<string | null>;
// Check upload status
hasPendingUpload(identifier: string): boolean;
isUploading(identifier: string): boolean;
// Cancel a pending upload
cancelUpload(identifier: string): Promise<void>;
// Clean up resources
close(): void;
}AssetProvider
Interface for implementing custom storage backends:
interface AssetProvider {
// Upload a blob to storage
upload(
blob: Blob,
identifier: string,
metadata?: AssetMetadata,
): Promise<AssetUploadResult>;
// Resolve an identifier to a displayable URL. `dimensions` is the device-pixel
// size the asset will render at — resizing providers (Cloudinary, imgix, etc.)
// can use it to return a matching variant; others can ignore it.
resolveUrl(
identifier: string,
dimensions: ResolveDimensions,
): Promise<string>;
// Optional: Delete an asset
delete?(identifier: string): Promise<void>;
// Configuration options
maxRetries?: number; // Max upload retry attempts (default: 3)
retryDelay?: number; // Retry delay in ms (default: 5000)
}
interface AssetMetadata {
filename?: string;
mimeType?: string;
}
interface AssetUploadResult {
url?: string; // Optional immediate URL
}
interface ResolveDimensions {
width: number; // Target render width in device pixels
height: number; // Target render height in device pixels
}LocalAssetProvider
Built-in provider that stores assets in IndexedDB (suitable for local-only apps or demos):
const provider = new LocalAssetProvider();
// Clean up when done
provider.close();Custom Provider Example
const myProvider: AssetProvider = {
async upload(blob, identifier, metadata) {
const formData = new FormData();
formData.append("file", blob, metadata?.filename);
formData.append("id", identifier);
const response = await fetch("/api/upload", {
method: "POST",
body: formData,
});
const { url } = await response.json();
return { url };
},
async resolveUrl(identifier, { width, height }) {
const response = await fetch(`/api/assets/${identifier}/url`);
const { url } = await response.json();
// e.g. for a resizing CDN: return `${url}?w=${width}&h=${height}`;
return url;
},
};License
MIT
