IndexedDB implementation of Virtual File System
npm install @firesystem/indexeddbIndexedDB implementation of the Virtual File System (VFS) interface. Provides persistent storage in web browsers with an intuitive file system API.
- 🌐 Browser Native - Uses IndexedDB for persistent storage
- 📦 Large Storage - Store gigabytes of data (browser limits apply)
- 🔄 Full VFS API - Implements complete @firesystem/core interface
- 🚀 Async Performance - Non-blocking operations
- 🔍 Indexing Support - Fast file lookups and queries
- 💾 Persistent - Data survives browser restarts
- ⚡ Native Events - Built-in reactive event system for all operations
- 📊 Progress Tracking - Monitor initialization and file loading
- 🔔 Real-time Updates - Get notified of all file system changes
- 🔌 Workspace Ready - Optional integration with @workspace-fs/core
- 🔄 Multi-tab Sync - Changes sync across browser tabs
``bash`
npm install @firesystem/indexeddbor
yarn add @firesystem/indexeddbor
pnpm add @firesystem/indexeddb
`typescript
import { IndexedDBFileSystem } from "@firesystem/indexeddb";
// Create a new file system instance
const fs = new IndexedDBFileSystem({
dbName: "my-app-storage", // Optional, defaults to "vfs"
version: 1, // Optional, defaults to 1
});
// Use it like any other VFS implementation
await fs.writeFile("/data.json", { message: "Hello, IndexedDB!" });
const file = await fs.readFile("/data.json");
console.log(file.content); // { message: "Hello, IndexedDB!" }
// NEW: Native event support
fs.events.on("file:written", ({ path, size }) => {
console.log(File ${path} saved to IndexedDB (${size} bytes));`
});
`typescript
interface IndexedDBConfig {
dbName?: string; // Database name (default: "vfs")
version?: number; // Database version (default: 1)
storeName?: string; // Object store name (default: "files")
enableExternalSync?: boolean; // Enable cross-tab sync and external change detection (default: true)
deepChangeDetection?: boolean; // Check content even if date hasn't changed (default: false)
}
const fs = new IndexedDBFileSystem({
dbName: "my-app",
version: 1,
storeName: "documents",
enableExternalSync: true, // Enable external change detection
});
`
IndexedDBFileSystem includes a native event system that emits events for all operations:
`typescript
import { IndexedDBFileSystem, FileSystemEvents } from "@firesystem/indexeddb";
const fs = new IndexedDBFileSystem({ dbName: "my-app" });
// Listen to file operations
fs.events.on(FileSystemEvents.FILE_WRITTEN, ({ path, size }) => {
console.log(File written: ${path} (${size} bytes));
});
fs.events.on(FileSystemEvents.FILE_DELETED, ({ path }) => {
console.log(File deleted: ${path});
});
// Listen to directory operations
fs.events.on(FileSystemEvents.DIR_CREATED, ({ path }) => {
console.log(Directory created: ${path});
});
// Operation tracking with timing
fs.events.on(FileSystemEvents.OPERATION_END, ({ operation, duration }) => {
console.log(${operation} completed in ${duration}ms);`
});
Load all existing files from IndexedDB with progress tracking:
`typescript
const fs = new IndexedDBFileSystem({ dbName: "my-app" });
// Track initialization progress
fs.events.on(FileSystemEvents.INIT_PROGRESS, ({ loaded, total, phase }) => {
console.log(${phase}: ${loaded}/${total} files loaded);
});
// Get notified of each file during initialization
fs.events.on(FileSystemEvents.FILE_READ, ({ path, size }) => {
console.log(Loaded: ${path} (${size} bytes));
});
fs.events.on(FileSystemEvents.DIR_READ, ({ path }) => {
console.log(Found directory: ${path});
});
// Initialize and load all existing files
await fs.initialize();
`
`typescript
// Example with React and Zustand
import { create } from 'zustand';
import { IndexedDBFileSystem, FileSystemEvents } from '@firesystem/indexeddb';
const useFileStore = create((set) => ({
files: [],
isLoading: true,
progress: { loaded: 0, total: 0 },
initializeFS: async () => {
const fs = new IndexedDBFileSystem({ dbName: "my-app" });
// Update progress
fs.events.on(FileSystemEvents.INIT_PROGRESS, ({ loaded, total }) => {
set({ progress: { loaded, total } });
});
// Populate files as they load
fs.events.on(FileSystemEvents.FILE_READ, ({ path, size }) => {
set(state => ({
files: [...state.files, { path, size, type: 'file' }]
}));
});
fs.events.on(FileSystemEvents.DIR_READ, ({ path }) => {
set(state => ({
files: [...state.files, { path, type: 'directory' }]
}));
});
// Mark as loaded when done
fs.events.on(FileSystemEvents.INITIALIZED, () => {
set({ isLoading: false });
});
// Initialize and load all files from IndexedDB
await fs.initialize();
}
}));
// In your component
function FileExplorer() {
const { files, isLoading, progress, initializeFS } = useFileStore();
useEffect(() => {
initializeFS(); // Load all files from IndexedDB
}, []);
if (isLoading) {
return
return
}
`
`typescript
// Monitor storage operations
fs.events.on(FileSystemEvents.STORAGE_CLEARING, () => {
console.log("Clearing all files...");
});
fs.events.on(FileSystemEvents.STORAGE_CLEARED, ({ duration }) => {
console.log(Storage cleared in ${duration}ms);
});
fs.events.on(FileSystemEvents.STORAGE_SIZE_CALCULATED, ({ size }) => {
console.log(Total storage used: ${size} bytes);`
});
IndexedDBFileSystem can be integrated with @workspace-fs/core through the IndexedDBWorkspaceProvider:
`typescript
import { IndexedDBFileSystem } from "@firesystem/indexeddb";
// Use directly without workspace
const fs = new IndexedDBFileSystem({ dbName: "my-app" });
await fs.initialize();
await fs.writeFile("/data.json", { message: "Hello!" });
`
`typescript
import { WorkspaceFileSystem } from "@workspace-fs/core";
import { indexedDBProvider } from "@firesystem/indexeddb/provider";
// Register the provider
const workspace = new WorkspaceFileSystem();
workspace.registerProvider(indexedDBProvider);
// Create persistent projects
const project = await workspace.loadProject({
id: "my-project",
name: "My Project",
source: {
type: "indexeddb",
config: {
dbName: "project-storage",
version: 1,
enableExternalSync: true,
},
},
});
// Work with the project - data persists across sessions
await workspace.writeFile("/src/app.js", "// Application code");
await workspace.writeFile("/data/users.json", JSON.stringify([]));
// Switch between multiple persistent projects
const devProject = await workspace.loadProject({
id: "dev",
name: "Development",
source: { type: "indexeddb", config: { dbName: "dev-db" } },
});
const prodProject = await workspace.loadProject({
id: "prod",
name: "Production",
source: { type: "indexeddb", config: { dbName: "prod-db" } },
});
await workspace.setActiveProject("dev");
`
The IndexedDB provider accepts all standard IndexedDBFileSystem configuration options:
`typescript
interface IndexedDBConfig {
// Database name (required for persistence)
dbName?: string;
// Database version (default: 1)
version?: number;
// Object store name (default: "files")
storeName?: string;
// Enable cross-tab sync (default: true)
enableExternalSync?: boolean;
// Deep change detection (default: false)
deepChangeDetection?: boolean;
}
`
IndexedDBFileSystem can detect changes made to the database from external sources (other tabs, windows, or direct IndexedDB access):
Changes made in one tab are automatically synchronized to other tabs using BroadcastChannel:
`typescriptChange detected: ${event.type} ${event.path}
// Tab 1
const fs1 = new IndexedDBFileSystem({ dbName: "my-app" });
fs1.watch("*/", (event) => {
console.log();
});
// Tab 2
const fs2 = new IndexedDBFileSystem({ dbName: "my-app" });
await fs2.writeFile("/data.json", { updated: true });
// Tab 1 will receive the change notification automatically
`
To detect changes made directly to IndexedDB (outside of FileSystem API):
`typescript
const fs = new IndexedDBFileSystem({ dbName: "my-app" });
// Start watching for external changes (default: 250ms polling)
await fs.startWatching(); // Poll every 250ms
// or with custom interval
await fs.startWatching(1000); // Poll every 1 second
// Your watch handlers will now receive notifications for external changes
fs.watch("*/", (event) => {
console.log(External change: ${event.type} ${event.path});
});
// Stop watching when done
await fs.stopWatching();
`
By default, changes are detected by file modification date and size. To detect content changes even when date/size remain the same:
`typescript
const fs = new IndexedDBFileSystem({
dbName: "my-app",
deepChangeDetection: true, // Enable content hash comparison
});
// Now changes will be detected even if:
// - File content changes but date doesn't update
// - Metadata changes but file size remains same
await fs.startWatching();
`
Note: Deep change detection has performance implications as it requires calculating hashes for all file contents. Use it only when necessary.
If you don't need external change detection:
`typescript`
const fs = new IndexedDBFileSystem({
dbName: "my-app",
enableExternalSync: false, // Disable all external sync features
});
IndexedDB storage limits vary by browser:
- Chrome/Edge: Up to 60% of total disk space
- Firefox: Up to 50% of free disk space
- Safari: Up to 1GB initially, can request more
Check available storage:
`typescriptUsing ${estimate.usage} of ${estimate.quota} bytes
if ("storage" in navigator && "estimate" in navigator.storage) {
const estimate = await navigator.storage.estimate();
console.log();`
}
`typescript
const fs = new IndexedDBFileSystem({ dbName: "offline-app" });
// Save user documents locally
await fs.writeFile("/documents/report.pdf", pdfBlob);
await fs.writeFile("/documents/data.xlsx", excelBlob);
// Access offline
const files = await fs.readDir("/documents");
`
`typescript
const fs = new IndexedDBFileSystem({ dbName: "web-ide" });
// Save project files
await fs.mkdir("/src");
await fs.writeFile("/src/index.js", sourceCode);
await fs.writeFile("/package.json", packageJson);
// Watch for changes
fs.watch("*/.js", (event) => {
console.log(File ${event.type}: ${event.path});`
});
`typescript
const fs = new IndexedDBFileSystem({ dbName: "app-cache" });
// Cache API responses
await fs.writeFile("/cache/users.json", await fetchUsers());
// Read from cache
try {
const cached = await fs.readFile("/cache/users.json");
return cached.content;
} catch {
// Cache miss, fetch fresh data
const fresh = await fetchUsers();
await fs.writeFile("/cache/users.json", fresh);
return fresh;
}
`
`typescript`
try {
await fs.writeFile("/data.json", data);
} catch (error) {
if (error.name === "QuotaExceededError") {
console.error("Storage quota exceeded");
// Handle cleanup or request more storage
}
}
1. Batch Operations: Group multiple operations when possible
2. Large Files: Consider chunking very large files
3. Indexing: Use meaningful paths for better organization
4. Event Listeners: Remove unused listeners to prevent memory leaks
5. Initialization: Call initialize() once to load all files efficiently
`typescript
// Dispose of event listeners when done
const disposable = fs.events.on(FileSystemEvents.FILE_WRITTEN, handler);
// Later...
disposable.dispose(); // Remove the listener
// Or remove all listeners for an event
fs.events.removeAllListeners(FileSystemEvents.FILE_WRITTEN);
`
Always dispose of the file system instance when done to clean up resources:
`typescript
const fs = new IndexedDBFileSystem({ dbName: "my-app" });
// Use the file system...
// Clean up when done
fs.dispose(); // Closes DB connection, stops watching, cleans up listeners
`
When testing code that uses IndexedDBFileSystem in Node.js environments, you have two options:
`typescript
// Import from the testing module which handles the polyfill automatically
import { IndexedDBFileSystem } from "@firesystem/indexeddb/testing";
describe("My app", () => {
it("should work with IndexedDB", async () => {
const fs = new IndexedDBFileSystem({ dbName: "test" });
await fs.writeFile("/test.txt", "Hello");
// ...
});
});
`
Note: You still need to install fake-indexeddb as a dev dependency:
`bash`
npm install --save-dev fake-indexeddb
`bash`
npm install --save-dev fake-indexeddbor
yarn add -D fake-indexeddbor
pnpm add -D fake-indexeddb
Then import it before your tests:
`typescript
// In your test setup file or at the top of test files
import "fake-indexeddb/auto";
// Now you can test IndexedDBFileSystem
import { IndexedDBFileSystem } from "@firesystem/indexeddb";
describe("My app", () => {
it("should work with IndexedDB", async () => {
const fs = new IndexedDBFileSystem({ dbName: "test" });
await fs.writeFile("/test.txt", "Hello");
// ...
});
});
``
See @firesystem/core for the complete VFS API documentation.
MIT © Anderson D. Rosa