Summary
We populate a LoroDoc from a plain JS object using loro-mirror (with defaultMovableList: true). We then try two ways to clone the document:
- JSON-updates round-trip:
doc.exportJsonUpdates() → newDoc.importJsonUpdates(...)
- Snapshot round-trip:
doc.export({ mode: 'snapshot' }) → LoroDoc.fromSnapshot(...)
Path (1) reproduces the state correctly. Path (2) does not — newDoc.toJSON() differs from doc.toJSON(), even though both methods are documented as full-state exports.
Investigating the snapshot itself in Loro Inspector will reveal that the snapshot is already missing data.
Reproduction
https://codesandbox.io/p/devbox/pxtpmw
import { LoroDoc } from 'loro-crdt'
import { Mirror } from 'loro-mirror'
const state = {
payload: { title: 'hello', count: 42 },
items: [
{ id: 'a', value: 1 },
{ id: 'b', value: 2 },
],
}
const doc = toLoroDoc(state)
console.log('Original document JSON', doc.toJSON())
// (1) clone via JSON updates
const updates = doc.exportJsonUpdates()
const newDoc = new LoroDoc()
newDoc.importJsonUpdates(updates)
console.log('Copy via JSON updates', newDoc.toJSON()) // <-- matches original
// (2) clone via snapshot
const snapshot = doc.export({ mode: 'snapshot' })
const newDoc2 = LoroDoc.fromSnapshot(snapshot)
console.log('Copy via snapshot', newDoc2.toJSON()) // <-- diverges from original
function toLoroDoc(state: any): LoroDoc {
const doc = new LoroDoc()
doc.setRecordTimestamp(true)
const mirror = createLoroMirror({ doc, initialState: state })
mirror.setState(() => state)
return doc
}
function createLoroMirror(options: any): any {
const { initialState, inferOptions } = options
return new Mirror({
...options,
initialState: emptyRootShape(initialState ?? {}),
inferOptions: { ...inferOptions, defaultMovableList: true },
validateUpdates: false,
})
}
function emptyRootShape(document: any): any {
return Object.fromEntries(
Object.entries(document).map(([key, value]) => {
if (Array.isArray(value)) return [key, []]
if (!!value && typeof value === 'object') return [key, {}]
return [key, value]
}),
)
}
Expected
After mirror.setState, the doc has a full operation history describing every container creation, map insertion, and list insertion. doc.export({ mode: 'snapshot' }) is documented as it should be exporting all changes, LoroDoc.fromSnapshot(...) should return document with whole state.
Environment
loro-crdt version: 1.12.0
loro-mirror version: 2.1.0
Node version: 22.22.0
Summary
We populate a LoroDoc from a plain JS object using loro-mirror (with defaultMovableList: true). We then try two ways to clone the document:
doc.exportJsonUpdates()→newDoc.importJsonUpdates(...)doc.export({ mode: 'snapshot' })→LoroDoc.fromSnapshot(...)Path (1) reproduces the state correctly. Path (2) does not — newDoc.toJSON() differs from doc.toJSON(), even though both methods are documented as full-state exports.
Investigating the snapshot itself in Loro Inspector will reveal that the snapshot is already missing data.
Reproduction
https://codesandbox.io/p/devbox/pxtpmw
Expected
After
mirror.setState, the doc has a full operation history describing every container creation, map insertion, and list insertion.doc.export({ mode: 'snapshot' })is documented as it should be exporting all changes,LoroDoc.fromSnapshot(...)should return document with whole state.Environment
loro-crdt version: 1.12.0
loro-mirror version: 2.1.0
Node version: 22.22.0