Persistence, assets and sharing
Host contracts for autosave, conflict detection, recovery, uploads and read-only sharing.
reopt designUpdated
1. Package and application responsibilities
onChange emits a validated document; it does not persist it. status is host-provided display text. Show a saved state only after the server acknowledges the relevant snapshot.
The repository Builder adapter implements EAV persistence, snapshots, recovery and sharing. That backend is not bundled into the npm package. Consumers can choose their own database or file storage.
2. Open and migrate documents
Treat external files and server responses as unknown. Use migrateDeckDocument for legacy v1 input and parseDeckDocument for APIs that only accept current v2 documents.
An open failure must not overwrite the current document or recovery copy with a blank document. Distinguish permission, parsing and asset-access failures and provide retry or recovery choices.
import {
migrateDeckDocument, parseDeckDocument,
} from "@reopt-ai/opt-deck/server";
export function openImportedJson(text: string) {
const input: unknown = JSON.parse(text);
// Accept legacy v1 and current v2. Throws on invalid content.
return migrateDeckDocument(input);
}
export function openCurrentServerRecord(payload: unknown) {
// Current v2 only. Use this at a host API boundary.
return parseDeckDocument(payload);
}3. Autosave and server versions
document.revision is an editing-history counter. Use a separate serverVersion and compare-and-swap for server concurrency. Serialize saves and queue the latest snapshot when new edits arrive during an in-flight request.
A 409 requires reconciliation of server content and the local draft, not automatic overwrite. The function below shows one save request only. The host adds debounce, queuing, retries and account-switch handling.
import type { DeckDocument } from "@reopt-ai/opt-deck/server";
export type StoredDeck = { document: DeckDocument; serverVersion: number };
// Host-defined endpoint and payload; not a built-in opt-deck API.
export async function saveSnapshot(
id: string, document: DeckDocument, serverVersion: number,
signal: AbortSignal,
): Promise<number> {
const response = await fetch("/api/presentations/" + encodeURIComponent(id), {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ document, expectedVersion: serverVersion }),
signal,
});
if (response.status === 409) throw new Error("Reload or reconcile the conflicting version");
if (!response.ok) throw new Error("Save failed; retain the local draft");
const result: unknown = await response.json();
if (!result || typeof result !== "object" || !("serverVersion" in result)
|| !Number.isSafeInteger(result.serverVersion)) {
throw new Error("Invalid save response");
}
return result.serverVersion as number;
}4. Asset upload contract
onAssetUpload receives a File and AbortSignal and returns a DeckAsset. Required fields are id, type, name, src, mimeType, bytes and embeddable. Types are image/font/video/audio/vector; fonts also use family, weight, style and embedding metadata.
Asset src URLs must remain accessible for editing, presentation and export. Do not treat an expiring signed URL as permanent storage. Discard completion after cancellation or document switches. Without a callback, local assets are embedded in the document.
import type { DeckEditorProps } from "@reopt-ai/opt-deck";
import { assetSchema } from "@reopt-ai/opt-deck/server";
export const uploadAsset: NonNullable<DeckEditorProps["onAssetUpload"]> =
async (file, signal) => {
const body = new FormData();
body.set("file", file);
// The host validates ownership, media type and file size.
const response = await fetch("/api/presentation-assets", {
method: "POST", body, signal,
});
if (!response.ok) throw new Error("Asset upload failed");
return assetSchema.parse(await response.json());
};5. Recovery and identity changes
Include account, project and document IDs in recovery keys and retain the base serverVersion. Do not start autosaving before the user resolves an old or conflicting recovery copy.
Cancel requests and ignore late responses on sign-out, identity changes or project switches. Remounting with a document-specific key prevents selection, camera and history from leaking between documents.