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.

Loading Demo...

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.

Loading Demo...

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.

Loading Demo...

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/memberContract
statuscommitted, pending, rejected, cancelled, stale, duplicate, noop.
transactionId, revision, diagnosticsAttempt ID, current committed revision and readable validation/ownership diagnoses.
subscribeCommitsCompleted { id, origin, baseRevision, model, patches, revision } events.
subscribeEntity(id, listener)Record updates for that ID; reads may return undefined after removal.
subscribePreviewsTransient node bounds/rotation previews, including cleared values.
Subscription returnUnsubscribe function; retain and call it.
notationModulesOptional 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