IndexedDB File Storage Implementation
Overview
This specification describes the behavior and implementation requirements for storing project files in IndexedDB instead of including them directly in the project JSON data structure. The implementation supports both local projects (persistent storage) and shared projects (in-memory storage).
Background
Previously, project files were stored as base64-encoded data URLs directly within the Project.files array. This approach had several limitations:
- Large file sizes caused performance issues
- JSON exports became very large (potentially hundreds of MB)
- Browser memory usage was excessive
- Serialization/deserialization was slow
Project Types
Local Projects
- User-created projects that persist across browser sessions
- Files stored in IndexedDB for persistence
- Can be edited, saved, and restored
Shared Projects
- Projects loaded from remote JSON URLs (read-only)
- Files stored in memory only (no IndexedDB persistence)
- Should not modify user’s local IndexedDB data
- Temporary viewing/fork-and-edit functionality
Requirements
1. File Storage Location
Requirement: Project files MUST be stored appropriately based on project type.
Implementation:
- Local Projects: Files stored in IndexedDB database
InkyCutFiles - Shared Projects: Files stored in memory using Map-based storage
- Database uses object store named
fileswith keyPathid - Files contain:
id,name,type,size,dataUrl,createdAt,width,height,duration
2. Project Data Structure
Requirement: The Project.files array MUST remain empty in localStorage and exported JSON, but MUST be populated with files when exporting to JSON.
Implementation:
Project.filesin localStorage:[](always empty)Project.filesin exported JSON: populated from storage (IndexedDB or memory)- Runtime access to files: through storage abstraction via
filesAtom
3. Storage Mode Detection
Requirement: The system MUST automatically detect whether to use IndexedDB or in-memory storage.
Implementation:
- Check if project is a shared/remote project
- Use in-memory storage for shared projects
- Use IndexedDB for local projects
- Provide API to explicitly set storage mode
4. File Upload Behavior
Requirement: When a user uploads a local file, it MUST be stored using the appropriate storage method.
Implementation:
- File converted to base64 data URL
- Metadata extracted: dimensions (width/height) for images/videos, duration for videos
- Stored using current storage mode (IndexedDB or memory)
Project.filesremains empty- UI updated via
filesAtomrefresh
5. JSON Import Behavior (Local Projects)
Requirement: When importing a JSON project file that contains files, those files MUST be extracted and stored in IndexedDB.
Implementation:
- Parse JSON and extract
filesarray - Clear existing files in IndexedDB
- Store imported files using
fileStorage.storeFiles() - Remove
filesfrom project data before storing in localStorage - Set
Project.files = []in the imported project
6. Shared Project Loading
Requirement: When loading a shared project, files MUST be stored in memory and NOT affect IndexedDB.
Implementation:
- Parse remote JSON and extract
filesarray - Store files in memory-only storage
- Do not clear or modify IndexedDB
- Allow fork-and-edit to migrate to IndexedDB storage
7. JSON Export Behavior
Requirement: When exporting a project to JSON, the exported file MUST include all files from current storage.
Implementation:
- Retrieve all files from current storage (IndexedDB or memory)
- Include files in the exported JSON structure
- Export contains complete project with files for portability
8. Clear Project Behavior
Requirement: When clearing/resetting a project, behavior MUST depend on project type.
Implementation:
- Local Projects: Clear IndexedDB files and reset project
- Shared Projects: Clear memory storage only, preserve IndexedDB
- Reset
filesAtomto empty array
9. Fork and Edit Behavior
Requirement: When forking a shared project, files MUST be migrated to IndexedDB storage.
Implementation:
- Copy all files from memory storage to IndexedDB
- Switch storage mode to IndexedDB
- Convert project to local project type
- Preserve all file data and references
10. File Access During Runtime
Requirement: Components MUST access files through the filesAtom which loads from current storage.
Implementation:
filesAtomis async and loads from current storage- Storage abstraction handles IndexedDB vs memory transparently
- Components use
useAtom(filesAtom)for file access - File operations update current storage and refresh
filesAtom
11. Error Handling
Requirement: File operations MUST handle storage errors gracefully.
Implementation:
- Wrap storage operations in try-catch blocks
- Log errors to console
- Provide user-friendly error messages
- Fallback to memory storage if IndexedDB fails
12. Storage Mode Switching
Requirement: System MUST support switching between storage modes.
Implementation:
- Provide API to switch from memory to IndexedDB (fork scenario)
- Migrate files during mode switch
- Clear old storage when appropriate
- Update atoms to reflect new storage mode
API Surface
FileStorage Interface
interface IFileStorage { // Storage mode readonly mode: 'indexeddb' | 'memory';
// Initialize storage init(): Promise<void>;
// File operations storeFile(file: LocalFile): Promise<void>; getFile(id: string): Promise<LocalFile | null>; getAllFiles(): Promise<LocalFile[]>; deleteFile(id: string): Promise<void>; clearAllFiles(): Promise<void>; storeFiles(files: LocalFile[]): Promise<void>;
// Storage info getStorageStats(): Promise<{ fileCount: number; totalSize: number; fileTypes: Record<string, number>; }>;
// Mode switching migrateToIndexedDB?(): Promise<void>;}FileStorage Factory
// Create storage instance based on modefunction createFileStorage(mode: 'indexeddb' | 'memory'): IFileStorage
// Get current storage mode for projectfunction getStorageModeForProject(project: Project): 'indexeddb' | 'memory'
// Switch storage mode and migrate filesasync function switchStorageMode( from: IFileStorage, to: IFileStorage): Promise<void>Updated Jotai Atoms
// Current storage modestorageModeAtom: Atom<'indexeddb' | 'memory'>
// Current file storage instancefileStorageAtom: Atom<IFileStorage>
// Load files from current storage (async)filesAtom: Atom<Promise<LocalFile[]>>
// Storage-aware file operationsaddFileAtom: WriteAtom<File>removeFileAtom: WriteAtom<string>clearAllFilesAtom: WriteAtom<void>importFilesAtom: WriteAtom<LocalFile[]>
// Fork shared project to localforkProjectAtom: WriteAtom<void>Data Flow
Local Project File Upload Flow
- User selects file via
LocalFileUploadcomponent - System detects local project → use IndexedDB storage
- File converted to
LocalFileobject with data URL and metadata (width, height, duration) addFileAtomstores file in IndexedDBfilesAtomrefreshed to show new file in UI
Shared Project Loading Flow
- Load remote JSON via
SharedProjectPage - Parse JSON and extract project data and files
- Create memory-based file storage
- Store files in memory using
importFilesAtom - Set project data without files (
files: []) - UI shows files from memory storage
Fork Shared Project Flow
- User clicks “Fork and Edit” on shared project
forkProjectAtomtriggered- Create IndexedDB storage instance
- Migrate all files from memory to IndexedDB
- Switch storage mode to IndexedDB
- Convert to local project
- Update all UI state
JSON Export Flow (Any Project Type)
- User clicks export in
ExportDialog - Get current project from
projectAtom - Get current storage from
fileStorageAtom - Get all files from current storage
- Merge files into project structure
- Export complete project JSON with files
Storage Implementations
IndexedDBStorage
- Persistent storage using browser IndexedDB
- Database:
InkyCutFiles, Store:files - Survives browser sessions and page reloads
- Used for local projects
MemoryStorage
- Temporary storage using JavaScript Map
- Cleared on page reload or navigation
- No persistence across sessions
- Used for shared/remote projects
Migration Strategy
Existing Local Projects
Projects with files in Project.files should be migrated automatically:
- Detect files in
Project.fileson project load - Move files to IndexedDB using
importFilesAtom - Clear
Project.filesarray - Update project in localStorage
Shared Project Access
For shared projects loaded from remote URLs:
- Parse remote JSON and extract files
- Create memory storage for session
- Store files in memory only
- Provide fork option to convert to local project
Browser Compatibility
- IndexedDB: Check
typeof indexedDB !== 'undefined' - Fallback: Use memory storage if IndexedDB unavailable
- Progressive Enhancement: Core functionality works without persistence
Performance Considerations
- Use async/await for all storage operations
- Lazy load files only when needed
- Batch operations for multiple files
- Provide progress feedback for large operations
- Memory cleanup for shared projects
Security Considerations
- Validate file types and sizes before storage
- Sanitize file data to prevent XSS
- Respect browser storage quotas
- Clear sensitive data on logout (if authentication added)
Testing Strategy
- Test both storage modes independently
- Test migration between storage modes
- Test shared project loading without IndexedDB interference
- Test fork-and-edit functionality
- Test error scenarios and fallbacks
- Performance testing with large files
- Browser compatibility testing
This implementation provides a robust, flexible file storage system that handles both local project persistence and shared project viewing while maintaining clean separation of concerns.