# How to Manage Project Persistence with SQLite and Auto-Save Middleware in Clypra

> Learn to manage project persistence in Clypra using SQLite and auto-save middleware. Discover how auto-save debounces changes and prevents corruption for a seamless user experience.

- Repository: [Abdulkabir Musa/Clypra](https://github.com/AIEraDev/Clypra)
- Tags: how-to-guide
- Published: 2026-07-16

---

**Clypra implements project persistence through a Tauri-backed SQLite database accessed via a platform API abstraction, with the Project Store ([`src/store/projectStore.ts`](https://github.com/AIEraDev/Clypra/blob/main/src/store/projectStore.ts)) orchestrating auto-save middleware that debounces changes every 500ms and prevents cross-project corruption by capturing project IDs at schedule time.**

Clypra is a video editing application built with Tauri that stores project metadata, timeline data, and media assets in a local SQLite database. The frontend TypeScript code never interacts directly with SQLite; instead, it delegates all persistence operations to a thin **platform layer** that forwards requests to Rust code using the `rusqlite` crate. This architecture ensures ACID compliance while keeping the frontend framework-agnostic.

## Architecture Overview

### The Platform API Abstraction

All database interactions flow through `src/core/platform/*`, which exposes methods like `platform.saveProject`, `platform.renameProject`, and `platform.deleteProject`. These functions act as a thin wrapper that forwards requests to the Rust backend, where actual SQLite queries execute against JSON-encoded project representations. This separation keeps the frontend persistence-agnostic while leveraging SQLite’s transactional guarantees.

### The Project Store Facade

The **Project Store** ([`src/store/projectStore.ts`](https://github.com/AIEraDev/Clypra/blob/main/src/store/projectStore.ts)) serves as the single entry point for persistence logic. It maintains the runtime session and ensures the timeline store remains the source of truth for editable state. When loading projects, the store calls `useTimelineStore.hydrateFromProject` to populate the timeline without direct mutation, preserving clean separation between the facade and domain layers.

## Auto-Save Middleware Implementation

### Debouncing with scheduleAutoSave

The **auto-save middleware** uses `scheduleAutoSave` to implement a **500ms debounce** (`AUTO_SAVE_DELAY`). When mutating actions like `addMediaAsset`, `updateProject`, or `removeMediaAsset` execute, they trigger this scheduler, which serializes the current in-memory state and invokes `platform.saveProject` to write back to SQLite.

This debouncing prevents excessive disk I/O during rapid edits while ensuring data durability.

### Preventing Cross-Project Corruption

The middleware captures the **current project ID at schedule time** to prevent race conditions when users switch projects during pending saves. If a user closes Project A and opens Project B while a save for Project A is still debouncing, the captured ID ensures the SQLite write targets the correct project, preventing cross-project corruption.

## SQLite Persistence Patterns

### Project Lifecycle Operations

The `loadProject` method reads saved projects from SQLite, deserializes the data, and creates a new runtime session via [`ProjectSession.ts`](https://github.com/AIEraDev/Clypra/blob/main/ProjectSession.ts). The `closeProject` method clears pending auto-save timers, performs a final save, and disposes the runtime session.

All SQLite interaction happens inside the platform layer, ensuring the frontend deals only with JavaScript objects.

### Crash Recovery via IndexedDB

After successful auto-saves, the store creates lightweight snapshots in **IndexedDB** through `saveSnapshot` (handled by [`src/core/runtime/CrashRecoveryService.ts`](https://github.com/AIEraDev/Clypra/blob/main/src/core/runtime/CrashRecoveryService.ts)). These snapshots enable crash recovery dialogs to restore user work if the application terminates unexpectedly, providing a secondary safety net beyond SQLite persistence.

## Practical Implementation Examples

Create a new project and let auto-save handle persistence automatically:

```typescript
// Create a new project – auto-save schedules automatically after creation
useProjectStore.getState().createProject('My Vacation', '16:9', 30);

```

Add media assets and trigger the debounced save:

```typescript
// Add a media asset – triggers scheduleAutoSave with 500ms debounce
useProjectStore.getState().addMediaAsset({
  id: generateId('media'),
  name: 'beach.mp4',
  type: 'video',
  path: '/Users/me/Videos/beach.mp4',
});

```

Manually trigger auto-save for testing or critical operations:

```typescript
// Force immediate auto-save consideration
useProjectStore.getState().scheduleAutoSave();

```

Load an existing project with full state hydration:

```typescript
// Load project – handles disposal, reset, hydration, and runtime session creation
await useProjectStore.getState().loadProject(savedProjectMeta, {
  tracks: savedTracks,
  clips: savedClips,
  transitions: savedTransitions,
  mediaAssets: savedMedia,
});

```

Gracefully close a project with final persistence:

```typescript
// Close project – clears timers, final save, disposes runtime
await useProjectStore.getState().closeProject();

```

## Summary

- **SQLite persistence** in Clypra is accessed exclusively through the **platform API** (`src/core/platform/*`), which forwards requests to Rust code using the `rusqlite` crate.
- The **Project Store** ([`src/store/projectStore.ts`](https://github.com/AIEraDev/Clypra/blob/main/src/store/projectStore.ts)) acts as a facade that orchestrates saves via `scheduleAutoSave`, implementing a **500ms debounce** to prevent excessive I/O.
- **Race condition protection** is achieved by capturing project IDs at schedule time, ensuring saves never corrupt the wrong project during rapid switching.
- **Crash recovery** relies on IndexedDB snapshots created after successful auto-saves, providing a backup restoration mechanism independent of SQLite.
- The timeline store remains the **single source of truth** for editable state, with hydration occurring only through `hydrateFromProject` during load operations.

## Frequently Asked Questions

### How does Clypra prevent data loss during rapid edits?

Clypra uses **debounced auto-save middleware** with a 500ms delay (`AUTO_SAVE_DELAY`) that batches rapid changes into a single SQLite write. This prevents excessive disk operations while ensuring the database reflects the latest state shortly after the user stops editing.

### Where does Clypra actually store project data on disk?

Project data is stored in a **SQLite database** managed by the Tauri Rust backend, accessed through the platform API. The JavaScript frontend never directly accesses the database file; all reads and writes flow through `platform.saveProject`, `platform.loadProject`, and related methods in `src/core/platform/*`.

### How does the auto-save middleware handle project switching?

The middleware captures the **project ID at the moment `scheduleAutoSave` is invoked**. If the user switches projects while a save is pending, the captured ID ensures the write operation targets the original project, preventing cross-contamination between different project files in SQLite.

### Can I manually trigger a project save outside the auto-save cycle?

Yes. While mutating actions like `addMediaAsset` automatically trigger `scheduleAutoSave`, you can manually invoke `useProjectStore.getState().scheduleAutoSave()` to force immediate consideration of pending changes, or use `closeProject()` to ensure a final save before application exit.