Diagram - Save and Load
Round-trip native JSON, acknowledge exact save checkpoints and recover application content without losing payloads.
#Usage
import { Diagram } from '@primeui/vue-diagram';
import { DiagramPersistenceController, type DiagramPersistenceSaver } from '@primeui/vue-diagram';
const persistence = new DiagramPersistenceController(store);<Diagram.Root :store="store" page-id="page">
<Diagram.Viewport label="Workflow editor" style="height: 24rem" />
</Diagram.Root>The controller retains the existing store across storage operations. The checkpoint examples below supply application storage callbacks and error handling.
This skeleton assumes an initialized store with page page. Use First Diagram for model creation, style imports and store disposal.
#UI Composition
import { DiagramPersistenceUI } from '@ui/diagram';<Diagram.Root :store="store" page-id="page">
<DiagramPersistenceUI :store="store" :controller="persistence" :loader="loader" :saver="saver" />
<Diagram.Viewport label="Document" style="height: 24rem" />
</Diagram.Root>Supply application-owned loader and saver callbacks. DiagramPersistenceSaver
is exported from @primeui/vue-diagram; the saver receives serialized text and
its request context. Resolve only after storing those exact bytes. Dispose an
application-owned controller before its store when their owner ends. The copied
UI does not dispose a supplied controller or supply storage.
The copied persistence controls present persistence state and invoke your application’s loader and saver callbacks. They do not supply storage.
Save a native model when you need to reopen the same Diagram content.
DiagramPersistenceController coordinates an application loader and saver with
the existing store. Your application owns storage, authentication and durability.
No storage service is bundled with Diagram.
#Use the copied persistence panel
The complete panel keeps Save/Load visible and puts manual native JSON behind an optional disclosure. Expanding it preserves the editor canvas below it.
The example's loader and saver use in-memory storage, not a server or browser persistence. Replace them with your application callbacks for durable storage.
For independent placement, bind the same inputs to Diagram.PersistenceHost,
then compose DiagramPersistenceActionsUI,
DiagramPersistenceImportUI and DiagramPersistenceStatusUI inside it. All three share
one DiagramPersistenceModel; they do not create separate save/load controllers.
DiagramPersistenceUI remains the complete composition with its own scoped model.
#Export and import native JSON
Export the current model, edit its title, then import the captured text. Try invalid import: the existing model and history remain unchanged.
serializeDiagramModel(model) returns a validated JSON result.
deserializeDiagramModel(text) returns a validated model result without
committing it. Check ok before reading value; failures contain diagnostics.
Call persistence.import(text) on your existing controller to validate and submit
the whole replacement as one transaction with import origin. Undo can reverse a
committed import; in controlled mode import instead waits for host acceptance.
Native JSON does not serialize sessions, renderer code or history stacks.
Native schemaVersion remains 1. Unsupported native versions, malformed JSON, invalid references and invalid geometry fail atomically. Native data and extensions must be inert JSON; they do not execute stored markup or callbacks. Exporting text alone does not acknowledge a durable save.
#Save an exact checkpoint through your application
Edit, start Save, then edit again during the delay. The saved checkpoint revision stays visible and newer edits stay dirty. Use Failure and retry scenario, fail a save, edit, then retry to see the original checkpoint saved again.
The example uses explicit local in-memory storage with a 1.4-second provider
delay. This abbreviated integration assumes your store and applicationStorage:
import { DiagramPersistenceController } from '@primeui/vue-diagram';
const persistence = new DiagramPersistenceController(store);
await persistence.save(async (text, { revision, requestId, signal }) => {
await applicationStorage.save({ text, revision, requestId, signal });
});The saver receives immutable serialized checkpoint bytes. Resolve its Promise
only when your application durably stored those exact bytes. Success lets the
controller call store.acknowledgeSave(checkpoint). remoteAcknowledged reports
provider success separately from whether local acknowledgement remained eligible.
| Store member | Contract |
|---|---|
createSaveCheckpoint() | Immutable { revision, model } tied to this store. |
acknowledgeSave(checkpoint) | Returns whether this store accepts the checkpoint; rejects a foreign/older checkpoint or unavailable store. |
acknowledgedSaveRevision | Revision of the last accepted save checkpoint; not necessarily the current revision. |
isDirty | Whether current model content differs from acknowledged saved content. |
revision | Current committed revision, independent of checkpoint revision. |
Edits during Save may commit normally. Saving the earlier checkpoint does not
mark newer content clean. retry() repeats the last operation explicitly; a
save retry reuses its original bytes and checkpoint and gets a new request
ID. To save newer edits, call save() again. A failed save never acknowledges
newer content, even if remote storage changed before its Promise rejected.
#Load, cancel and handle stale work
load(loader) expects a Promise of native JSON or an explicitly supported
application envelope. Start Load, then edit or Cancel before it completes.
A newer commit, editing preview or proposal makes that load stale; the old result
cannot replace intervening work. cancel() settles the local job immediately.
It does not undo a host proposal already submitted or roll back remote storage.
The controller has one latest-wins operation lane. Starting a newer operation locally cancels the prior operation. This protects local application; it does not order writes on your service. Use conditional revisions, idempotency or storage serialization to prevent a late old save from overwriting newer remote bytes. Reconcile uncertain provider outcomes using your storage protocol.
Providers receive { requestId, revision, signal }. The portable signal exposes
aborted and abort listener add/remove methods, rather than being a DOM
AbortSignal. Forward it to your own transport controller when needed and remove
listeners in finally. The local example removes its timer/listener on completion
or abort. Providers remain responsible for transport cleanup.
| Controller member/state | Contract |
|---|---|
Constructor (store, codec?) | Stable store; default codec is native JSON with no application profiles. |
import(text) | Synchronous decode plus atomic replacement transaction. |
load(loader), save(saver), retry() | Promise of final local state; retry with no prior operation reports an error. |
cancel(), dispose() | End local owned work; disposal also removes controller subscriptions. |
state, subscribe(listener) | Current snapshot and observable updates; subscription returns an unsubscribe function. |
status | idle, loading, saving, saved, committed, pending, noop, error, cancelled, stale, disposed. |
requestId, checkpointRevision | Optional attempt ID and saved checkpoint revision. |
remoteAcknowledged, transaction | Optional provider-success fact and store result; host acceptance remains separate. |
diagnostics | Array of { code, path, message }; show the actionable message near the operation. |
Pending proposals/previews block starting persistence work. Preview/proposal
ownership during a save can invalidate its local acknowledgement even when the
provider succeeded. Provider failure reports provider-error; invalid input
reports codec diagnostics. Resolve controlled proposals at the store, as shown
in Controlled State.
#Recover missing application content
The saved account starts as an Unavailable content placeholder. Enable the application renderer and its availability declaration together: the original balance and extension data render without a reload or migration.
Root contentAvailability (bound with :content-availability="availability") defaults null and leaves existing rendering
behavior unchanged. An explicit declaration can contain nodeTypes, symbols
and extensions, each an optional string array. Omit a category to leave it
unassessed; use [] to declare that none are available.
inspectDiagramRecovery(model, availability) returns readonly diagnostics
without changing payloads. Node-owned missing type/extension content displays a
placeholder even when a generic template exists. IDs, geometry, endpoints,
type, data and extensions remain attached. Recovery diagnostics also inspect
extension keys on the model, pages, layers, ports and labels. Symbol diagnostics
respect installed symbol IDs and valid embedded definitions; use the
Symbols guide for symbol registration/recovery.
Availability is your application's declaration, not a plugin downloader. Register your existing content template/component and update availability together. Restoring rendering is not a model transaction or history edit.
#Register application-defined migration envelopes
This example imports a synthetic application format. Application version 1→2 is independent of native Diagram schema 1.
DiagramPersistenceCodec(profiles = []) accepts registered profiles. Each has
format, positive safe-integer currentVersion, and steps with from, to
and a pure synchronous migrate(payload) callback. Format matches
[A-Za-z][A-Za-z0-9._-]{0,79}. The envelope contains exactly
{ format, version, payload }; place persistent metadata in model data or
extensions instead of extra envelope fields.
codec.decode(text) returns { ok: true, model, migrations } or
{ ok: false, diagnostics } without committing. migrations lists applied
{ from, to } steps. The final payload must validate as native schema-1 content.
Save always emits native JSON; it does not emit your application envelope.
Profiles are copied/frozen at construction. Duplicate formats/start versions, backward or same-version steps, and steps beyond the target fail registration. Unknown format, future version, a chain gap or a failed migration produce separate diagnostics. Callbacks receive detached frozen inert JSON and must be pure, deterministic and bounded. They are supplied by your application; Core does not load code from the envelope. Synchronous callbacks cannot be preempted.
Limits are 16 profiles, 32 steps per profile/chain, 16 million UTF-16 text units, depth 64 and one million visited inert values. Envelopes use the same limits. Native Diagram models use schema version 1; these migration profiles apply only to registered application envelopes.
Keep the same controller and store across refreshes. Follow the cleanup order in SSR and Lifecycle.
Restart example restores the local fixture and clears its history. It does not reset an external service.