@veltdev/nutrient-crdt
v1.0.1
Published
Velt CRDT wrapper for Nutrient Web SDK
Readme
@veltdev/nutrient-crdt
Vanilla Velt CRDT binding for Nutrient Web SDK documents.
Install
npm install @veltdev/nutrient-crdt @nutrient-sdk/viewer @veltdev/client @veltdev/crdt yjs y-protocolsUse @veltdev/nutrient-crdt-dev only when validating the development build output from this repository.
What It Syncs
The package stores Nutrient Instant JSON in a Velt CRDT Store with type: "map".
The default content key is document.
The wrapper strips volatile pdfId values before persistence.
Annotations, form field values, comments, bookmarks, and supported Instant JSON metadata are included in the shared snapshot.
Remote selections and cursors are published through Velt awareness because they are ephemeral presence state.
Quick Start
import NutrientViewer from '@nutrient-sdk/viewer';
import { createCollaboration } from '@veltdev/nutrient-crdt';
const instance = await NutrientViewer.load({
container: document.querySelector('#viewer') as HTMLElement,
document: '/sample.pdf',
useCDN: true,
});
const manager = await createCollaboration({
editorId: 'contract-2026',
veltClient,
instance,
});Core APIs
createCollaboration(config) creates and initializes a CollaborationManager.
manager.attachInstance(instance) binds an existing Nutrient viewer instance.
manager.flushInstanceToStore(reason, true) exports Instant JSON and updates the Velt Store immediately.
manager.applyInstantJson(instantJson) replaces the shared document snapshot.
manager.addHighlight(input) adds an Instant JSON highlight annotation.
manager.addComment(input) adds a portable comment payload.
manager.setFormFieldValue(name, value) updates Instant JSON form field values.
manager.saveVersion(name), getVersions(), getVersionById(id), restoreVersion(id), and setStateFromVersion(version) delegate to Velt CRDT version APIs.
manager.forceReset(initialContent) replaces the shared state and reapplies it locally.
manager.detachInstance() unbinds the viewer; manager.destroy() releases all subscriptions and overlays.
manager.setLocalSelection(selection) and publishCurrentSelection(selection) publish ephemeral presence selections through awareness.
manager.getRemoteSelections(), renderRemoteSelections(container), and clearRemoteSelectionOverlays() read and render remote presence overlays.
manager.onStatusChange(cb), onSynced(cb), onRemoteSelectionsChange(cb), and onDocumentChange(cb) subscribe to status, sync, presence, and document changes; each returns an unsubscribe function.
manager.getStore(), getDoc(), getMap(), getProvider(), getAwareness(), getInstance(), getDocumentState(), getInstantJson(), and getStats() expose typed escape hatches and diagnostics.
Pure Instant JSON helpers (createDocumentState, mergeInstantJson, addHighlightToInstantJson, addCommentToInstantJson, setFormFieldValue, stripPdfId, diffInstantJson, getInstantJsonRecordId, and more) are exported for advanced snapshot handling.
Collaboration Model
Nutrient does not expose a public Yjs document model for Instant JSON.
This wrapper stores full Instant JSON snapshots (with per-record envelopes) rather than operation-level PDF annotation CRDTs.
Non-overlapping edits converge through the latest shared Instant JSON snapshot.
Overlapping writes to the same annotation, comment, or form field resolve by the latest accepted Store update.
Remote changes are applied per record: the manager diffs against a baseline and calls instance.create/update/delete/setFormFieldValues with only the delta, keeping the viewer's annotation layer flicker-free and the PDF unrewritten.
Annotation identity travels in the record name (viewer-local ids are stripped from shared state) and is mapped to local viewer ids on receive.
Pass the imported @nutrient-sdk/viewer module as the sdk config option so remote records are materialized with real SDK classes; records a viewer rejects are skipped locally and remain synced in the Store.
Whole-document applyInstantJson is used only for viewer instances without granular record methods.
Document-native attachments (base64 binaries) are not synced; unchanged Store keys are never rewritten, keeping per-change transport bounded.
Applications with large documents should profile Instant JSON payload size and flush frequency.
Licensing
Nutrient Web SDK can run in trial mode without a license key.
Production deployments should pass a valid licenseKey when creating the Nutrient viewer.
Comments and some document editing features may require Nutrient license capabilities.
The wrapper is framework-neutral and does not bundle a Nutrient license.
Documentation
See the repository demo/INTEGRATION.md for a complete Vite example, Velt initialization, multi-user testing, versions, persistence, awareness selections, and cleanup.
See known-issues.md for snapshot conflict semantics and feature limitations.
