Configuring IndexedDB Persistence for Pascal Editor Scene Data
Pascal Editor stores binary user assets in IndexedDB via idb-keyval while persisting the lightweight scene graph JSON to localStorage, using a custom asset:// protocol to bridge the two storage mechanisms.
The pascalorg/editor repository implements a dual-tier persistence strategy that separates heavy binary data from lightweight application state. While scene hierarchy and node data serialize to localStorage for rapid hydration, uploaded images, 3D models, and audio files persist in IndexedDB through the asset-storage.ts module. This architecture keeps the in-memory scene store lightweight while enabling offline asset reuse.
Understanding the Dual Persistence Architecture
Pascal Editor manages two distinct data categories with separate storage backends:
- Scene Graph (Nodes, Hierarchy, UI State): Serialized as JSON to
localStorageviascene.ts, with undo/redo handled in-memory by Zundo. - User Assets (Images, 3D Files, Audio): Binary blobs stored in IndexedDB via
asset-storage.tsand referenced through a customasset://protocol.
This separation ensures that heavy binary data never enters the Zustand store, keeping undo/redo operations fast while still allowing assets to survive browser refreshes.
Storing Binary Assets in IndexedDB
All binary asset persistence flows through packages/core/src/lib/asset-storage.ts, which wraps IndexedDB operations using the idb-keyval library.
The Asset Storage Flow
When a user uploads a file, the system executes a three-step persistence process:
- Write:
saveAsset(file)stores theBlobin IndexedDB under a generated UUID and returns anasset://<uuid>URL. - Reference: Scene nodes store this URL in properties like
ItemNode.asset.url. - Resolve:
loadAssetUrl(url)fetches the blob from IndexedDB, creates anObjectURL(blob:), and caches it to avoid repeatedURL.createObjectURLcalls.
Saving User Uploads to IndexedDB
Import saveAsset from the core package to persist uploaded files and receive a referenceable URL:
import { saveAsset } from '@pascal-app/core/lib/asset-storage'
async function handleUpload(file: File) {
// Returns "asset://d7e4c1a2-..." and stores blob in IndexedDB
const assetUrl = await saveAsset(file)
// Attach to scene node
const newItem = ItemNode.parse({
asset: { url: assetUrl, category: 'furniture' }
})
useScene.getState().createNode(newItem, parentId)
}
Source: packages/core/src/lib/asset-storage.ts (lines 11-15)
Resolving Asset URLs for Rendering
At render time, convert the stored asset:// reference back to a usable blob URL:
import { loadAssetUrl } from '@pascal-app/core/lib/asset-storage'
async function getTextureUrl(nodeAssetUrl: string): Promise<string | null> {
// Returns "blob:https://..." or original URL if not asset://
return await loadAssetUrl(nodeAssetUrl)
}
Source: packages/core/src/lib/asset-storage.ts (lines 21-30)
Persisting Scene Graph State to localStorage
Unlike assets, the scene graph itself never touches IndexedDB. Instead, packages/editor/src/lib/scene.ts provides localStorage helpers for JSON serialization.
Serializing Scene Data
Capture the current editor state and write it to localStorage:
import { saveSceneToLocalStorage } from '@pascal-app/editor/lib/scene'
function exportCurrentScene() {
const { nodes, rootNodeIds } = useScene.getState()
saveSceneToLocalStorage({ nodes, rootNodeIds })
}
Source: packages/editor/src/lib/scene.ts (lines 72-80)
Restoring Editor State on Initialization
Hydrate the application on startup by loading the persisted graph and applying it to the editor:
import { loadSceneFromLocalStorage, applySceneGraphToEditor } from '@pascal-app/editor/lib/scene'
function initApp() {
const saved = loadSceneFromLocalStorage()
applySceneGraphToEditor(saved) // Restores nodes + UI selection
}
Source: packages/editor/src/lib/scene.ts (lines 82-90)
State Management Architecture
The persistence strategy relies on specific middleware and store structures:
useSceneStore (packages/core/src/store/use-scene.ts): Central Zustand store managing nodes and mutations. Wrapped with Zundo (zundo.temporal) to keep the last 50 actions in memory for undo/redo, avoiding disk I/O during history navigation.- Viewer Preferences (
packages/viewer/src/store/use-viewer.ts): UI state like theme, camera mode, and level mode persists viazustand/middlewarepersist, but this excludes scene nodes. - idb-keyval: Declared in
packages/core/package.json, this minimal promise-based wrapper handles all IndexedDB transactions for asset storage.
Summary
- Binary assets persist in IndexedDB via
asset-storage.tsusing theasset://URI scheme andidb-keyval. - Scene graphs serialize to
localStorageas JSON throughscene.ts, enabling fast initial loads. - Undo/redo operates entirely in memory via Zundo, ensuring heavy blobs never slow down history navigation.
- Asset resolution converts stored
asset://URLs to temporaryblob:URLs at runtime for rendering.
Frequently Asked Questions
Why does Pascal Editor use localStorage for the scene graph instead of IndexedDB?
The scene graph consists of lightweight JSON describing nodes and hierarchy. Storing it in localStorage provides synchronous read/write access needed for rapid state hydration, while keeping IndexedDB reserved for large binary blobs prevents the Zustand store from becoming bloated and maintains fast in-memory undo/redo performance via Zundo.
What library handles IndexedDB operations in the Pascal Editor codebase?
The codebase uses idb-keyval, a minimal promise-based wrapper around the IndexedDB API. This dependency is declared in packages/core/package.json and consumed exclusively in packages/core/src/lib/asset-storage.ts for saveAsset and loadAssetUrl operations.
How does the editor reference stored assets in the scene graph?
Scene nodes store string URLs in the format asset://<uuid> (for example, asset://d7e4c1a2-...). The loadAssetUrl function in asset-storage.ts detects this protocol, retrieves the corresponding blob from IndexedDB, generates a temporary blob: URL via URL.createObjectURL, and caches the result to avoid repeated database lookups during rendering.
Where is UI preference data persisted in the Pascal Editor?
User interface preferences—including theme settings, camera mode, and level mode—are persisted using Zustand's persist middleware in packages/viewer/src/store/use-viewer.ts. This storage mechanism is separate from both the scene graph (localStorage) and binary assets (IndexedDB).
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 →