Diagram - Controlled State
Choose managed commits or an explicit host proposal protocol, and keep connection approval separate.
#Usage
import { Diagram } from '@primeui/vue-diagram';<Diagram.Root :store="store" page-id="page">
<Diagram.Viewport label="Workflow editor" style="height: 24rem" />
</Diagram.Root>Create the store with { mode: 'controlled' }. Subscribe to proposals and acknowledge the matching transaction and revision before presenting an edit as saved.
This skeleton assumes an initialized store with page page. Use First Diagram for model creation and style imports, and SSR and lifecycle for caller-owned store disposal.
Choose mode: 'managed' to commit valid edits immediately. Choose mode: 'controlled'
when your application must accept or reject each proposal: an uncommitted model
change. Neither mode merges concurrent authors' changes or provides collaborative Undo.
#Choose ownership
Use :value="model" for a root-owned managed store with default configuration.
Use useDiagramStore(initialModel) in setup when managed
state must exist before rendering or be shared between roots. That helper reads
once and owns disposal. It does not accept store options.
Controlled mode, notation modules, custom history limits, and transaction callbacks
use createDiagramStore(model, options) and :store="store". The caller
handles the factory result and disposes its store after consumers detach. The
root never disposes a supplied store.
Supply exactly one source. value is a reload input, separate from the controlled
proposal protocol. Do not echo accepted commit models into value: every new
reference starts a fresh store, history, and save baseline. Continue to use
acknowledge and synchronize for a controlled store.
Create a controlled store in its owner, with an already validated application
model. Detach consumers before disposing that store on teardown:
import { createDiagramStore } from '@primeui/vue-diagram';
const created = createDiagramStore(model, { mode: 'controlled' });
if (!created.ok) throw new Error(created.diagnostics.map((item) => item.message).join('; '));
const store = created.value;Root v-model:value is unsupported. Only v-model:connection-type controls the
separate future-connection preference. Use @commit to observe completed edits;
do not feed each commit model back into root value.
#Start with managed store edits
Drag a card or change its title. A valid edit commits immediately. Try the invalid two-command batch: neither card changes.
createDiagramStore(model, options?) returns { ok: true, value: store } or
{ ok: false, diagnostics }. Validate that result before mounting the root.
Defaults are mode: 'managed', revision: 0, historyLimit: 100 and receiptLimit: 4096.
getSnapshot() and getRecord(collection, id) return immutable records; do not
mutate them. getEntity, getOrder, getIncoming and getOutgoing provide
record/index reads without constructing a second model.
Submit a host edit with transact({ id, origin, baseRevision?, commands }).
Use a unique transaction ID. A supplied base revision must match the current
revision. origin.kind is user, api, import, remote, undo or redo;
optional actorId identifies the host actor without changing ownership.
Commands support add, replace, update, remove, reorder for pages,
layers, nodes and connectors, plus replace-model. Add accepts an optional
insertion index. Reorder supplies the entire collection order. Removing a
referenced record requires resolving its references in the same batch.
All commands validate together, so an invalid last command cannot leave earlier
changes committed. beforeTransaction(proposal) may veto with false; exceptions
reject. Observers cannot roll back a completed commit. Optional
onObserverError(error) handles isolated observer errors.
Editor actions such as session.edit check current session permissions.
The host can still write directly to the store. Enforce application rules in those
transactions and acknowledgements; read-only blocks editor actions.
#Propose, accept, reject or cancel
Propose a move and inspect committed x: it stays unchanged until the host accepts. Reject or cancel another proposal. Then accept an edit, propose again, synchronize a newer host snapshot and try accepting the remembered proposal. Use Stale acknowledgement and synchronization to try a mismatched base while pending.
A controlled transaction returns pending and exposes pendingProposal:
{ id, origin, baseRevision, model, patches }. The proposal model is the
immutable candidate; getSnapshot() remains the committed model.
subscribeProposals(listener) observes candidates and subscribeTransactions
observes their outcomes. The root exposes completed commit events; proposal and transaction
observation use the store subscriptions.
This abbreviated handler assumes a current proposal and store:
store.acknowledge({
transactionId: proposal.id,
status: 'accepted',
baseRevision: proposal.baseRevision,
revision: proposal.baseRevision + 1
});Acceptance requires the exact proposal ID and base revision, with the accepted
revision immediately following that base. Reject with
{ transactionId, status: 'rejected', baseRevision }, or cancel the current
proposal with cancelProposal(proposal.id). Neither commits the candidate nor
adds history. A mismatched current proposal ID/base is stale and leaves that proposal pending.
A retained finalized transaction ID returns duplicate before matching its base;
it does not apply twice. Newer synchronization finalizes the pending proposal
with a stale outcome, so accepting its remembered ID returns duplicate while its receipt is retained. A second local
transaction cannot take ownership while a proposal is pending.
Cancelling or disposing a binding, persistence or clipboard controller does not cancel a proposal already submitted to the host. Decide or cancel that proposal explicitly at the store.
#Choose a receipt window
The default receiptLimit: 4096 retains the most recent 4,096 finalized transaction
IDs. Set a positive safe integer for another capacity. Oldest finalized IDs expire
first; duplicate reads do not renew them. Retained IDs return duplicate.
Expired IDs follow normal validation and may apply again. This finite default
changes the previous Store-lifetime duplicate guarantee.
Use unique IDs for every new operation. If transport retries must remain
idempotent beyond this window, keep a durable application ledger. For the previous
Store-lifetime behavior, explicitly set receiptLimit: 'lifetime'; receipt memory
then grows until disposal.
Pending proposals remain protected independently until accepted, rejected,
cancelled or superseded. Stale acknowledgements leave the current proposal
pending. After its finalized receipt expires, an old acknowledgement is stale
unless a new proposal has reused that ID. Receipt capacity is independent of
historyLimit, dirty state and saved checkpoints. Evaluated no-ops, rejected
commands, cancellations, Undo/Redo and successful synchronization share the
window; preflight failures and unchanged synchronization echoes do not consume it.
#Synchronize a newer host snapshot
store.synchronize(model, revision, transactionId, origin?) accepts a newer
validated authoritative host model in controlled mode. The default origin is
{ kind: 'remote' }. A successful synchronization invalidates the pending
proposal and previews, clears Undo/Redo history and emits a commit without a new
proposal. Its revision may advance beyond one step.
Equal content at the current revision returns noop; conflicting same-revision
or older snapshots are stale. Do not synchronize using the pending proposal's
ID as an echo: resolve that proposal through acknowledge first. Invalid
snapshots leave the committed model unchanged. Session selection/focus are
reconciled against surviving visible records; synchronization is not a merge of
concurrent authors' changes.
#Approve connections separately
Request a connection, approve its endpoints, then accept the host proposal. Reject or cancel at the connection stage to create nothing. Reject at the host stage to leave the candidate uncommitted.
Root connectionOptions.requireApproval defaults false. When true,
session.connections exposes an immutable request { id, revision, command }.
Call resolve(request.id, true | false) once, or cancel() before submission.
Acceptance rechecks revision, geometry, policy and preview ownership. Only then
does it submit the store transaction. In controlled mode that transaction still
needs host acceptance. onApprovalRequest(request) is an optional application
notification callback; it does not accept the host proposal.
Connection status distinguishes pending-approval from pending-store and
committed. Paste and duplicate can use the same composite connection approval
lane. Cancelling after store submission does not withdraw the host proposal.
See Connector Rules for endpoint policy.
#Read results and clean up
| Store result/member | Contract |
|---|---|
status | committed, pending, rejected, cancelled, stale, duplicate, noop. |
transactionId, revision, diagnostics | Attempt ID, current committed revision and readable validation/ownership diagnoses. |
subscribeCommits | Completed { id, origin, baseRevision, model, patches, revision } events. |
subscribeEntity(id, listener) | Record updates for that ID; reads may return undefined after removal. |
subscribePreviews | Transient node bounds/rotation previews, including cleared values. |
| Subscription return | Unsubscribe function; retain and call it. |
notationModules | Optional fixed creation-time semantic validators/reference owners; configure before creating the store. |
Use Vue refs for visible pending/error feedback. Do not treat a provider's success or a connection approval as permission to accept a host proposal. Unsubscribe your observers and dispose an application-owned store when its owner is destroyed; the Diagram root disposes its sessions.
Restart example restores the local fixture and clears its history. It does not reset an external service.
Data Binding ยท History ยท Permissions ยท Events and commands