Skip to main content

Persistence

When you modify @field properties, Idyllic automatically persists changes to durable storage. You don’t write database code, configure serialization, or explicitly save data. This guide explains what persists, when, and how to work with the system effectively.

What Persists Automatically

Everything marked with @field persists. After each action completes, Idyllic serializes field values to SQLite storage within the Durable Object. When the system wakes—whether seconds or days later—state restores exactly as you left it.
export default class TaskManager extends AgenticSystem {
  @field tasks: Task[] = [];
  @field settings = { theme: 'dark' };
  @field lastSync: Date | null = null;

  @action()
  addTask(title: string) {
    this.tasks.push({ id: crypto.randomUUID(), title, done: false });
    // Persists automatically when action completes
  }
}
Every task added through addTask stores durably. Close your browser, restart the server, return a week later—this.tasks contains everything you added. No explicit save call needed. Stream fields persist their current value. When streaming completes, accumulated content saves. Clients connecting afterward see the final result.

What Does Not Persist

Properties without @field exist only in memory. They reset when the Durable Object hibernates or restarts.
export default class MySystem extends AgenticSystem {
  @field count = 0;      // Persists

  cache = new Map();     // Lost on restart
  tempData = [];         // Lost on restart
}
Use non-field properties for temporary caches, computed values, or data you don’t want to persist.

When Persistence Happens

State writes to storage after each @action method completes successfully and when streams complete or fail. The write happens asynchronously—no blocking I/O in your action methods. If an action throws before completing, state changes roll back. This transactional behavior keeps state consistent even when errors occur.

Serialization Requirements

State stores as JSON. All values must be JSON-serializable:
TypeSupportedNotes
PrimitivesYesstring, number, boolean, null
Plain objectsYes
ArraysYes
Maps, SetsYesSerialized/restored correctly
DatesYesSerialized as ISO string
FunctionsNoCannot serialize
Class instancesNoLose prototype chain

Handling Schema Changes

As your application evolves, state shape may change. Handle migrations in onCreate():
@field version = 2;
@field tasks: Task[] = [];
@field settings = { theme: 'dark', notifications: true };

onCreate() {
  if ((this as any).version === 1) {
    this.settings.notifications = true;
    this.version = 2;
  }
}
Include a version number, check it on load, apply transformations.

The SQL Escape Hatch

For complex queries on large datasets, Idyllic provides direct SQL access:
@action()
async getRecentEvents(limit: number) {
  return await this.sql`
    SELECT * FROM events
    ORDER BY timestamp DESC
    LIMIT ${limit}
  `;
}
Use this for append-only logs or datasets too large to load into memory.

FAQ

How much data can I store?

Durable Objects support gigabytes. For most applications—conversation histories, task lists—tens of thousands of items work fine.

What’s the read/write latency?

Reads from state are instant (property access in memory). Writes persist asynchronously after actions complete—no blocking I/O.

Can I query across instances?

No. Each instance has isolated storage. For cross-instance queries, use an external database.

How do I back up data?

Create an export action:
@action()
exportData() {
  return { tasks: this.tasks, settings: this.settings };
}