Save and restore
Keep source authoritative, commit its matching preview atomically and reject stale or released requests.
A structured host saves TextDiagramEmbed: protocol version, stable id, title, source language/text/revision and the matching PNG. A new draft may have no preview. A saved object should carry a preview generated from that exact source.
Save source and image together
createTextDiagram creates revision 0. renderTextDiagram creates a source-bound snapshot, rasterizeTextDiagram generates its PNG, and prepareTextDiagramSave proposes a complete object with revision + 1. The workbench manages this pipeline already.
readTextDiagram validates unknown input and returns an immutable copy. It rejects extra fields, accessors and noncanonical values. It validates structure and size; it does not re-render to prove that the PNG matches the source. Structural validation alone does not make an untrusted image a verified diagram projection.
serializeTextDiagram provides a stable comparison string. After parsing JSON, validate it again with readTextDiagram before passing it to a host.
A complete local host
When a user opens a diagram, the workbench eventually returns an edited result. The application must save it and check that another action has not changed the original while the dialog was open.
createDiagramSession(initial, persist) maintains the accepted version of one diagram. read() returns the current diagram. capture() records the opening version and supplies commit() to accept a result and release() to end that edit. The application supplies a synchronous persist callback, such as a local-storage write. If it throws, the accepted revision remains unchanged and success is not reported.
| File | Purpose |
|---|---|
recipes/session.ts | Compare opening and current versions, call storage, then update the accepted diagram |
shared/types.ts | Shared types for the edit request and session |
import { readTextDiagram, serializeTextDiagram, type TextDiagramEmbed } from "@quirekit/text-diagram";
import type { DiagramSession } from "../shared/types";
/**
* Creates a single-object host with compare-and-save semantics.
* @param initial - Validated persisted object or new draft.
* @param persist - Optional synchronous storage write; throwing leaves the baseline unchanged.
* @returns A host whose requests cannot overwrite a newer accepted revision.
*/
export function createDiagramSession(initial: TextDiagramEmbed, persist?: (next: TextDiagramEmbed) => void): DiagramSession {
let current = readTextDiagram(initial);
return {
/** @returns The immutable accepted revision. */
read() { return current; },
/** @returns A detached baseline with explicit commit and release callbacks. */
capture() {
const baseline = current, expected = serializeTextDiagram(baseline);
let released = false;
return {
document: baseline,
/** @param proposal - Source and preview proposed by the workbench. @returns Atomic host acceptance. */
commit(proposal) {
if (released) return "missing";
if (serializeTextDiagram(current) !== expected) return "conflict";
const next = readTextDiagram(proposal);
if (next.id !== baseline.id || next.document.revision !== baseline.document.revision + 1 || !next.preview) {
throw new TypeError("Invalid diagram save");
}
// Storage must succeed before publishing the new revision to the UI.
persist?.(next);
current = next;
return "saved";
},
/** Closes this target without changing the accepted object. */
release() { released = true; },
};
},
};
}
The workbench supplies an object with a matching preview. A production host must also check that the target exists, remains editable and still maps to a valid position at commit time. Classic, Block and MSFE own those editor semantics. This example does not implement collaboration or cross-tab synchronization.
Four acceptance results
| Result | Meaning | UI behavior |
|---|---|---|
saved | Host atomically accepted source and preview | Report success |
conflict | Content changed after the captured baseline | Keep draft; explain conflict |
readonly | Writes are no longer allowed | Keep draft; explain read-only state |
missing | Target was removed or released | Keep draft; explain missing target |
Storage failures can throw. The workbench displays failure and allows retry. This contract does not provide asynchronous server confirmation, automatic merging or backend retry; those belong to the host's persistence strategy.
Markdown hosts
Use textDiagramMarkdown(language, source) to generate a standard fence. It chooses a delimiter longer than any backtick run in the source, preventing an accidental closing fence. Markdown retains language and source, without a structured id, title, PNG or revision. Its source host owns editing and conflict detection.