Skip to main content

Overview

The Persistence API provides filesystem tree serialization and storage management with support for multiple backends. It includes automatic debouncing for efficient saves and graceful error handling.

PersistenceManager

Manages the lifecycle of filesystem persistence with automatic debouncing and error recovery.

Constructor

new PersistenceManager(backend: PersistenceBackend)
backend
PersistenceBackend
required
The storage backend implementation (e.g., IndexedDBPersistenceBackend or MemoryPersistenceBackend)

Methods

open

Initializes the persistence backend.
async open(): Promise<void>
Example:
const backend = new IndexedDBPersistenceBackend();
const manager = new PersistenceManager(backend);
await manager.open();

load

Loads the filesystem tree from persistent storage.
async load(): Promise<INode | null>
return
INode | null
Returns the deserialized root node, or null if no saved state exists or deserialization fails
Example:
const root = await manager.load();
if (root) {
  console.log('Restored filesystem from storage');
} else {
  console.log('No saved state found');
}

save

Immediately saves the filesystem tree to persistent storage.
async save(root: INode): Promise<void>
root
INode
required
The root node of the filesystem tree to persist
Example:
await manager.save(rootNode);
Save errors are gracefully ignored to prevent disrupting the application. Use scheduleSave for automatic debouncing.

scheduleSave

Schedules a debounced save operation. Automatically cancels pending saves.
scheduleSave(root: INode): void
root
INode
required
The root node of the filesystem tree to persist
Example:
// Multiple rapid calls will only trigger one save after 1 second
manager.scheduleSave(rootNode);
manager.scheduleSave(rootNode); // Cancels previous
manager.scheduleSave(rootNode); // Only this one executes
The debounce interval is 1000ms (1 second). See PersistenceManager.ts:5

PersistenceBackend Interface

Defines the contract for persistence storage implementations.
interface PersistenceBackend {
  open(): Promise<void>;
  loadTree(): Promise<SerializedNode | null>;
  saveTree(root: SerializedNode): Promise<void>;
  close?(): Promise<void>;
}
open
() => Promise<void>
required
Initialize the storage backend
loadTree
() => Promise<SerializedNode | null>
required
Load the serialized filesystem tree. Returns null if not found.
saveTree
(root: SerializedNode) => Promise<void>
required
Save the serialized filesystem tree
close
() => Promise<void>
Optional cleanup method to close connections and release resources

IndexedDBPersistenceBackend

Browser-based persistent storage using IndexedDB.

Constructor

new IndexedDBPersistenceBackend()

Configuration

  • Database name: lifo (see backends.ts:18)
  • Object store: filesystem (see backends.ts:19)
  • Storage key: root (see backends.ts:20)

Methods

open

Opens or creates the IndexedDB database.
async open(): Promise<void>
Automatically creates the object store on first use. Gracefully degrades if IndexedDB is unavailable.

loadTree

Retrieves the filesystem tree from IndexedDB.
async loadTree(): Promise<SerializedNode | null>
return
SerializedNode | null
The serialized filesystem tree, or null if not found or on error

saveTree

Persists the filesystem tree to IndexedDB.
async saveTree(root: SerializedNode): Promise<void>
root
SerializedNode
required
The serialized filesystem tree to store

close

Closes the IndexedDB connection.
async close(): Promise<void>

Example

const backend = new IndexedDBPersistenceBackend();
await backend.open();

// Save
const serializedTree = serialize(rootNode);
await backend.saveTree(serializedTree);

// Load
const loaded = await backend.loadTree();
if (loaded) {
  const restored = deserialize(loaded);
}

// Cleanup
await backend.close();

MemoryPersistenceBackend

In-memory storage for testing and temporary filesystems.

Constructor

new MemoryPersistenceBackend()

Methods

open

async open(): Promise<void>
No-op for in-memory storage.

loadTree

async loadTree(): Promise<SerializedNode | null>
return
SerializedNode | null
The in-memory serialized tree, or null if nothing has been saved

saveTree

async saveTree(root: SerializedNode): Promise<void>
root
SerializedNode
required
The serialized filesystem tree to store in memory

close

async close(): Promise<void>
Clears the in-memory storage.

Example

const backend = new MemoryPersistenceBackend();
await backend.open();

const serialized = serialize(rootNode);
await backend.saveTree(serialized);

const loaded = await backend.loadTree();
console.log(loaded === serialized); // true

await backend.close(); // Clears memory
Use MemoryPersistenceBackend for unit tests or when persistence between sessions is not needed.

SerializedNode Format

Compact representation of filesystem nodes.
interface SerializedNode {
  t: 'f' | 'd';              // Type: file or directory
  n: string;                  // Name
  d?: string;                 // Base64 data (small files only)
  c?: SerializedNode[];       // Children (directories only)
  ct: number;                 // Creation time (Unix timestamp)
  mt: number;                 // Modification time (Unix timestamp)
  m: number;                  // Unix mode/permissions
  mi?: string;                // MIME type (files only)
  br?: string;                // Blob store reference hash
  ch?: SerializedChunkRef[];  // Chunk manifest (large files)
  sz?: number;                // Stored size (when chunked)
}
Virtual directories (proc, dev) are excluded during serialization. See serializer.ts:22

Build docs developers (and LLMs) love