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.

Loading Demo...

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.

Loading Demo...

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.

Loading Demo...

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 memberContract
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.
acknowledgedSaveRevisionRevision of the last accepted save checkpoint; not necessarily the current revision.
isDirtyWhether current model content differs from acknowledged saved content.
revisionCurrent 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/stateContract
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.
statusidle, loading, saving, saved, committed, pending, noop, error, cancelled, stale, disposed.
requestId, checkpointRevisionOptional attempt ID and saved checkpoint revision.
remoteAcknowledged, transactionOptional provider-success fact and store result; host acceptance remains separate.
diagnosticsArray 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.

Loading Demo...

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.

Loading Demo...

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.

Controlled State · History