4.6 KiB
Project Storage Architecture
Oak persists projects in a database (SQLite by default, PostgreSQL
supported), with write-through persistence: every edit is committed
to the database as it happens — there is no Save button and nothing to
lose. The .ove XML file, OTIO and FCPXML are import/export formats,
not the working store.
Design principles
- Aggregate-granular persistence, not full relational mapping. The database stores four things: project metadata, key/value settings, periodic full snapshots, and a per-node command journal. Domain semantics (timeline structure, node connections, keyframes, effect chains) stay inside each node's XML payload. A project is exactly "the node graph plus settings" — nothing else — so node granularity is a closed, complete model.
- One serialization truth. The node XML in the database is the same
document the
.oveserializer produces (oaknode::serializer). New features (e.g. adjustment layers) extend the XML schema only — the database schema never changes. - The journal is produced by diffing, not by instrumenting commands. After every undoable command the in-memory project is re-serialized and compared node-by-node with the previous state; changed/added/ removed nodes each become one journal row. Existing and future command types are covered automatically.
- The journal is the persistent undo history. Replaying applies each node's newest image in command order; rewinding applies the previous images in reverse. Undo survives restarts.
Schema (SQLite / PostgreSQL, sea-orm)
projects(id PK, uuid UNIQUE, name, schema_ver, created_at, modified_at, command_seq)
settings(project_id FK, key, value, PK(project_id, key))
snapshots(project_id FK, command_seq, payload, written_at, PK(project_id, command_seq))
journal(project_id FK, seq, node_identity, kind, old_xml, new_xml, at,
PK(project_id, seq, node_identity))
snapshots.payloadis the full project XML, written everyStorage/SnapshotIntervalSec(default 600) while dirty, pruned to the newest 3. It only accelerates loading — the journal alone can rebuild the project from scratch.journalrows are whole-node before/after images:old_xmlis NULL for created nodes,new_xmlis NULL for removed nodes. Loading takes the newest snapshot and replaces each node with its newest image inseqorder; rewinding to commandNappliesold_xmlbackwards.node_identity = 0is the settings pseudo-node.- Every command is one synchronous transaction (journal rows +
command_seq + 1), so akill -9loses zero commands. Storage/JournalRetentionDays(default 0 = keep forever) bounds the undo window; rows are kilobytes each.
Timeline representation
The timeline is a chain of node references inside node XML:
sequence ──<tracklists>──▶ tracklist ──<tracks>──▶ track ──<blocks>──▶ clip ──<footage>──▶ footage
The clip row carries its timeline range (<range in out/>), media
offset (<media_in>) and footage reference; effect chains are
connection records inside the effect nodes' XML. Loading re-links
these identities in two passes (see oaknode::serializer), so no
join tables are needed. Timeline edits map to a handful of node rows:
moving a clip touches its track and the clip; splitting adds one node
and updates two; ripple edits touch the affected tracks and delete the
removed clips.
Write-through flow
- A facade undo push (
oakengine_undo_push, group end, undo/redo/ jump) succeeds. - The project is re-serialized in memory (microseconds to low milliseconds) and diffed against the last state.
- One transaction writes the changed node rows, bumps
command_seqandmodified_at. - A background thread writes snapshots latest-wins; on exit the queue is flushed.
Import / export
- Import:
.ove/.otio/.fcpxmlare parsed by their existing oakstorage backends and inserted as a new project row withkind = 'import'journal entries. - Export: the in-memory serialization is written through the ove-xml or otio backend; nothing is read from or written to the database beyond the current state.
Multi-writer and platforms
v1 assumes a single writer per database (SQLite busy_timeout, PG row
locks). Multi-writer collaboration is future work (M14). The default
database is a single user-level SQLite file; PostgreSQL is selected
with an oakdb+pg:// URI.
See also: M10 oakstorage manual, M13 write-through plan, project file reference.