Commands
Every edit is a validated, serializable, undoable command.
Commands are the only way to change the document. A command is plain JSON: a type and a payload.
.({ : "clip/move", : { : "intro", : 1_000_000 } });| Property | What it gives you |
|---|---|
| Validated | Payloads are checked against a Zod schema before anything runs |
| Atomic | A failed command throws and leaves state untouched |
| Deterministic | Same state + same command = same result. Required for collaboration and replay |
| Undoable | Each command records its inverse; see History |
| Serializable | Log, sync, store or generate them with an LLM |
Built-in commands
| Group | Commands |
|---|---|
| Project | project/set-settings |
| Assets | asset/add, asset/remove, asset/set-property → Assets |
| Tracks | track/add, track/remove, track/reorder, track/rename, track/set-property → Tracks |
| Clips | clip/add, clip/remove, clip/move, clip/trim, clip/split, clip/duplicate, clip/set-property → Clips |
| Keyframes | keyframe/set, keyframe/remove, keyframe/clear → Keyframes |
| Effects | effect/add, effect/update, effect/remove, effect/reorder → Effects |
| Transitions | transition/add, transition/update, transition/remove → Transitions |
Every payload field of every built-in command is listed in the Commands reference. At runtime, project.commandCatalog() returns the same thing as JSON Schema, including your custom commands, and you can hand it to an LLM as its tools: Tools from the command catalog.
Errors
dispatch throws one of three errors. State is never changed when it throws.
| Error | When | Useful fields |
|---|---|---|
UnknownCommandError | No command with that type is registered | commandType |
CommandValidationError | The payload doesn't match the schema | commandType, issues (Zod issues) |
CommandRejectedError | The payload is valid but can't apply to the current state | commandType, code |
import { , } from "@miraiclip/core";
try {
.({ : "asset/remove", : { : "intro-mp4" } });
} catch () {
if ( instanceof && . === "asset-in-use") {
// remove the clips that use it first
} else if ( instanceof ) {
.(.);
} else {
throw ;
}
}Common rejection codes:
| Code | Cause |
|---|---|
track-not-found, clip-not-found | The id doesn't exist |
duplicate-id | An entity with that id already exists |
kind-mismatch | The track doesn't accept this clip kind |
asset-kind-mismatch | E.g. a video clip pointing at an audio asset |
asset-in-use | Removing an asset that clips still use |
Without exceptions
tryDispatch returns a result instead of throwing. Its failures are plain data, which suits AI agents and form validation.
import { } from "@miraiclip/core";
const = (, { : "clip/move", : { : "nope", : 0 } });
if (!.) {
..; // "unknown-command" | "invalid-payload" | "rejected"
..; // 'Command "clip/move" rejected (clip-not-found): no clip "nope"'
}Several commands as one step
Wrap related commands in a transaction. They undo and redo together, and if one throws, the earlier ones are rolled back. More in History.
.(() => {
.({ : "clip/split", : { : "intro", : 2_000_000, : "intro-b" } });
.({ : "clip/remove", : { : "intro-b" } });
}, "Trim end");Custom commands
Register your own command types. They get the same validation, history, patches and catalog entry as built-ins.
import { CommandRejectedError } from "@miraiclip/core";
import { z } from "zod";
project.registerCommand({
type: "clip/nudge",
schema: z.object({ clipId: z.string(), byUs: z.number().int() }),
handler: (doc, { clipId, byUs }) => {
const clip = doc.clips[clipId];
if (!clip) throw new CommandRejectedError("clip/nudge", "clip-not-found", `No clip ${clipId}`);
clip.startUs = Math.max(0, clip.startUs + byUs);
},
});
project.dispatch({ type: "clip/nudge", payload: { clipId: "intro", byUs: 500_000 } });| Rule | Why |
|---|---|
The handler mutates doc, a draft | Changes are recorded as patches and inverses (Immer) |
Throw CommandRejectedError to refuse | The draft is discarded and state stays untouched |
Keep handlers pure: no Date.now(), Math.random() or I/O | Replays and collaborators must get the same result |
| Register on every project instance | Registration is per project, not global |
Custom commands use zod v4 schemas (the version @miraiclip/core uses).