Events & patches
Know exactly what changed, and sync it to other clients.
const off = project.events.on("patches", ({ patches, inverse, source }) => {
console.log(source, patches);
// "dispatch" [{ op: "replace", path: "/clips/intro/startUs", value: 1000000 }]
});
off(); // stop listeningEvents
| Event | Payload | Fires |
|---|---|---|
patches | { patches, inverse, source } | Every document change |
history | { kind, label? } | A commit, undo or redo; see History |
playhead | { positionUs } | setPlayhead, including every frame during playback |
selection | { ids } | setSelection |
project.events.on returns an unsubscribe function. project.events.off(event, listener) works too.
For UI state, prefer project.subscribe(selector, listener): it fires only when the value you render changes. Use events when you need what changed.
Patches
patches are RFC 6902 JSON Patch operations (add, replace, remove) with paths relative to the document root. inverse undoes them.
source | Meaning |
|---|---|
dispatch | A command ran |
undo / redo | History moved; one event per step, even for a transaction |
transaction-rollback | A failed transaction reverted its earlier changes |
A transaction emits one patches event per command as it runs, then a single history commit at the end.
Syncing other clients
Patches are what make collaboration possible. Send them to peers and apply them to their copy of the document:
import { applyJsonPatches, type JsonPatchOp, type ProjectDocument } from "@miraiclip/core";
// Sender: forward every event, rollbacks included, in order
project.events.on("patches", ({ patches }) => send(patches));
// Receiver: a read-only mirror of the document
let mirror: ProjectDocument = project.toJSON();
function onRemotePatches(patches: JsonPatchOp[]) {
mirror = applyJsonPatches(mirror, patches); // returns a new document; the input isn't mutated
}
declare function send(patches: JsonPatchOp[]): void;applyJsonPatches handles the add, replace and remove operations the engine emits.
Patches or commands?
Patches mirror a document exactly, which suits viewers, previews and server-side copies. For two people editing at once, send commands instead and dispatch them on every client: each client validates a command against its own current state, so an edit that no longer applies is rejected instead of being written blindly. See Collaboration.
Other uses
| Use | How |
|---|---|
| Autosave | Debounce on patches, then save project.toJSON() |
| Dirty flag | Set on patches, clear after saving |
| Audit log | Store patches with a timestamp and user |
| Targeted UI updates | Check patch.path, e.g. /clips/<id>/…, and refresh only that clip's row |