Merge pull request #243 from yjs/create-doc-from-snapshot-3
Create doc from snapshot 3
This commit is contained in:
		
						commit
						81f16ff0b5
					
				@ -47,6 +47,7 @@ export {
 | 
				
			|||||||
  findRootTypeKey,
 | 
					  findRootTypeKey,
 | 
				
			||||||
  typeListToArraySnapshot,
 | 
					  typeListToArraySnapshot,
 | 
				
			||||||
  typeMapGetSnapshot,
 | 
					  typeMapGetSnapshot,
 | 
				
			||||||
 | 
					  createDocFromSnapshot,
 | 
				
			||||||
  iterateDeletedStructs,
 | 
					  iterateDeletedStructs,
 | 
				
			||||||
  applyUpdate,
 | 
					  applyUpdate,
 | 
				
			||||||
  applyUpdateV2,
 | 
					  applyUpdateV2,
 | 
				
			||||||
 | 
				
			|||||||
@ -12,13 +12,17 @@ import {
 | 
				
			|||||||
  createDeleteSet,
 | 
					  createDeleteSet,
 | 
				
			||||||
  createID,
 | 
					  createID,
 | 
				
			||||||
  getState,
 | 
					  getState,
 | 
				
			||||||
  AbstractDSDecoder, AbstractDSEncoder, DSEncoderV1, DSEncoderV2, DSDecoderV1, DSDecoderV2, Transaction, Doc, DeleteSet, Item // eslint-disable-line
 | 
					  findIndexSS,
 | 
				
			||||||
 | 
					  UpdateEncoderV2,
 | 
				
			||||||
 | 
					  DefaultDSEncoder,
 | 
				
			||||||
 | 
					  applyUpdateV2,
 | 
				
			||||||
 | 
					  AbstractDSDecoder, AbstractDSEncoder, DSEncoderV2, DSDecoderV1, DSDecoderV2, Transaction, Doc, DeleteSet, Item // eslint-disable-line
 | 
				
			||||||
} from '../internals.js'
 | 
					} from '../internals.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import * as map from 'lib0/map.js'
 | 
					import * as map from 'lib0/map.js'
 | 
				
			||||||
import * as set from 'lib0/set.js'
 | 
					import * as set from 'lib0/set.js'
 | 
				
			||||||
import * as decoding from 'lib0/decoding.js'
 | 
					import * as decoding from 'lib0/decoding.js'
 | 
				
			||||||
import { DefaultDSEncoder } from './encoding.js'
 | 
					import * as encoding from 'lib0/encoding.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class Snapshot {
 | 
					export class Snapshot {
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@ -148,3 +152,51 @@ export const splitSnapshotAffectedStructs = (transaction, snapshot) => {
 | 
				
			|||||||
    meta.add(snapshot)
 | 
					    meta.add(snapshot)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {Doc} originDoc
 | 
				
			||||||
 | 
					 * @param {Snapshot} snapshot
 | 
				
			||||||
 | 
					 * @param {Doc} [newDoc] Optionally, you may define the Yjs document that receives the data from originDoc
 | 
				
			||||||
 | 
					 * @return {Doc}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const createDocFromSnapshot = (originDoc, snapshot, newDoc = new Doc()) => {
 | 
				
			||||||
 | 
					  if (originDoc.gc) {
 | 
				
			||||||
 | 
					    // we should not try to restore a GC-ed document, because some of the restored items might have their content deleted
 | 
				
			||||||
 | 
					    throw new Error('originDoc must not be garbage collected')
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  const { sv, ds } = snapshot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const encoder = new UpdateEncoderV2()
 | 
				
			||||||
 | 
					  originDoc.transact(transaction => {
 | 
				
			||||||
 | 
					    let size = 0
 | 
				
			||||||
 | 
					    sv.forEach(clock => {
 | 
				
			||||||
 | 
					      if (clock > 0) {
 | 
				
			||||||
 | 
					        size++
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    encoding.writeVarUint(encoder.restEncoder, size)
 | 
				
			||||||
 | 
					    // splitting the structs before writing them to the encoder
 | 
				
			||||||
 | 
					    for (const [client, clock] of sv) {
 | 
				
			||||||
 | 
					      if (clock === 0) {
 | 
				
			||||||
 | 
					        continue
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (clock < getState(originDoc.store, client)) {
 | 
				
			||||||
 | 
					        getItemCleanStart(transaction, createID(client, clock))
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      const structs = originDoc.store.clients.get(client) || []
 | 
				
			||||||
 | 
					      const lastStructIndex = findIndexSS(structs, clock - 1)
 | 
				
			||||||
 | 
					      // write # encoded structs
 | 
				
			||||||
 | 
					      encoding.writeVarUint(encoder.restEncoder, lastStructIndex + 1)
 | 
				
			||||||
 | 
					      encoder.writeClient(client)
 | 
				
			||||||
 | 
					      // first clock written is 0
 | 
				
			||||||
 | 
					      encoding.writeVarUint(encoder.restEncoder, 0)
 | 
				
			||||||
 | 
					      for (let i = 0; i <= lastStructIndex; i++) {
 | 
				
			||||||
 | 
					        structs[i].write(encoder, 0)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    writeDeleteSet(encoder, ds)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  applyUpdateV2(newDoc, encoder.toUint8Array(), 'snapshot')
 | 
				
			||||||
 | 
					  return newDoc
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,7 @@ import * as encoding from './encoding.tests.js'
 | 
				
			|||||||
import * as undoredo from './undo-redo.tests.js'
 | 
					import * as undoredo from './undo-redo.tests.js'
 | 
				
			||||||
import * as compatibility from './compatibility.tests.js'
 | 
					import * as compatibility from './compatibility.tests.js'
 | 
				
			||||||
import * as doc from './doc.tests.js'
 | 
					import * as doc from './doc.tests.js'
 | 
				
			||||||
 | 
					import * as snapshot from './snapshot.tests.js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { runTests } from 'lib0/testing.js'
 | 
					import { runTests } from 'lib0/testing.js'
 | 
				
			||||||
import { isBrowser, isNode } from 'lib0/environment.js'
 | 
					import { isBrowser, isNode } from 'lib0/environment.js'
 | 
				
			||||||
@ -16,7 +17,7 @@ if (isBrowser) {
 | 
				
			|||||||
  log.createVConsole(document.body)
 | 
					  log.createVConsole(document.body)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
runTests({
 | 
					runTests({
 | 
				
			||||||
  doc, map, array, text, xml, encoding, undoredo, compatibility
 | 
					  doc, map, array, text, xml, encoding, undoredo, compatibility, snapshot
 | 
				
			||||||
}).then(success => {
 | 
					}).then(success => {
 | 
				
			||||||
  /* istanbul ignore next */
 | 
					  /* istanbul ignore next */
 | 
				
			||||||
  if (isNode) {
 | 
					  if (isNode) {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										171
									
								
								tests/snapshot.tests.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								tests/snapshot.tests.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,171 @@
 | 
				
			|||||||
 | 
					import { createDocFromSnapshot, Doc, snapshot, YMap } from '../src/internals'
 | 
				
			||||||
 | 
					import * as t from 'lib0/testing.js'
 | 
				
			||||||
 | 
					import { init } from './testHelper'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {t.TestCase} tc
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const testBasicRestoreSnapshot = tc => {
 | 
				
			||||||
 | 
					  const doc = new Doc({ gc: false })
 | 
				
			||||||
 | 
					  doc.getArray('array').insert(0, ['hello'])
 | 
				
			||||||
 | 
					  const snap = snapshot(doc)
 | 
				
			||||||
 | 
					  doc.getArray('array').insert(1, ['world'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const docRestored = createDocFromSnapshot(doc, snap)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  t.compare(docRestored.getArray('array').toArray(), ['hello'])
 | 
				
			||||||
 | 
					  t.compare(doc.getArray('array').toArray(), ['hello', 'world'])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {t.TestCase} tc
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const testEmptyRestoreSnapshot = tc => {
 | 
				
			||||||
 | 
					  const doc = new Doc({ gc: false })
 | 
				
			||||||
 | 
					  const snap = snapshot(doc)
 | 
				
			||||||
 | 
					  snap.sv.set(9999, 0)
 | 
				
			||||||
 | 
					  doc.getArray().insert(0, ['world'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const docRestored = createDocFromSnapshot(doc, snap)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  t.compare(docRestored.getArray().toArray(), [])
 | 
				
			||||||
 | 
					  t.compare(doc.getArray().toArray(), ['world'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // now this snapshot reflects the latest state. It shoult still work.
 | 
				
			||||||
 | 
					  const snap2 = snapshot(doc)
 | 
				
			||||||
 | 
					  const docRestored2 = createDocFromSnapshot(doc, snap2)
 | 
				
			||||||
 | 
					  t.compare(docRestored2.getArray().toArray(), ['world'])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {t.TestCase} tc
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const testRestoreSnapshotWithSubType = tc => {
 | 
				
			||||||
 | 
					  const doc = new Doc({ gc: false })
 | 
				
			||||||
 | 
					  doc.getArray('array').insert(0, [new YMap()])
 | 
				
			||||||
 | 
					  const subMap = doc.getArray('array').get(0)
 | 
				
			||||||
 | 
					  subMap.set('key1', 'value1')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const snap = snapshot(doc)
 | 
				
			||||||
 | 
					  subMap.set('key2', 'value2')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const docRestored = createDocFromSnapshot(doc, snap)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  t.compare(docRestored.getArray('array').toJSON(), [{
 | 
				
			||||||
 | 
					    key1: 'value1'
 | 
				
			||||||
 | 
					  }])
 | 
				
			||||||
 | 
					  t.compare(doc.getArray('array').toJSON(), [{
 | 
				
			||||||
 | 
					    key1: 'value1',
 | 
				
			||||||
 | 
					    key2: 'value2'
 | 
				
			||||||
 | 
					  }])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {t.TestCase} tc
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const testRestoreDeletedItem1 = tc => {
 | 
				
			||||||
 | 
					  const doc = new Doc({ gc: false })
 | 
				
			||||||
 | 
					  doc.getArray('array').insert(0, ['item1', 'item2'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const snap = snapshot(doc)
 | 
				
			||||||
 | 
					  doc.getArray('array').delete(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const docRestored = createDocFromSnapshot(doc, snap)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  t.compare(docRestored.getArray('array').toArray(), ['item1', 'item2'])
 | 
				
			||||||
 | 
					  t.compare(doc.getArray('array').toArray(), ['item2'])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {t.TestCase} tc
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const testRestoreLeftItem = tc => {
 | 
				
			||||||
 | 
					  const doc = new Doc({ gc: false })
 | 
				
			||||||
 | 
					  doc.getArray('array').insert(0, ['item1'])
 | 
				
			||||||
 | 
					  doc.getMap('map').set('test', 1)
 | 
				
			||||||
 | 
					  doc.getArray('array').insert(0, ['item0'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const snap = snapshot(doc)
 | 
				
			||||||
 | 
					  doc.getArray('array').delete(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const docRestored = createDocFromSnapshot(doc, snap)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  t.compare(docRestored.getArray('array').toArray(), ['item0', 'item1'])
 | 
				
			||||||
 | 
					  t.compare(doc.getArray('array').toArray(), ['item0'])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {t.TestCase} tc
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const testDeletedItemsBase = tc => {
 | 
				
			||||||
 | 
					  const doc = new Doc({ gc: false })
 | 
				
			||||||
 | 
					  doc.getArray('array').insert(0, ['item1'])
 | 
				
			||||||
 | 
					  doc.getArray('array').delete(0)
 | 
				
			||||||
 | 
					  const snap = snapshot(doc)
 | 
				
			||||||
 | 
					  doc.getArray('array').insert(0, ['item0'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const docRestored = createDocFromSnapshot(doc, snap)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  t.compare(docRestored.getArray('array').toArray(), [])
 | 
				
			||||||
 | 
					  t.compare(doc.getArray('array').toArray(), ['item0'])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {t.TestCase} tc
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const testDeletedItems2 = tc => {
 | 
				
			||||||
 | 
					  const doc = new Doc({ gc: false })
 | 
				
			||||||
 | 
					  doc.getArray('array').insert(0, ['item1', 'item2', 'item3'])
 | 
				
			||||||
 | 
					  doc.getArray('array').delete(1)
 | 
				
			||||||
 | 
					  const snap = snapshot(doc)
 | 
				
			||||||
 | 
					  doc.getArray('array').insert(0, ['item0'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const docRestored = createDocFromSnapshot(doc, snap)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  t.compare(docRestored.getArray('array').toArray(), ['item1', 'item3'])
 | 
				
			||||||
 | 
					  t.compare(doc.getArray('array').toArray(), ['item0', 'item1', 'item3'])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {t.TestCase} tc
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const testDependentChanges = tc => {
 | 
				
			||||||
 | 
					  const { array0, array1, testConnector } = init(tc, { users: 2 })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!array0.doc) {
 | 
				
			||||||
 | 
					    throw new Error('no document 0')
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (!array1.doc) {
 | 
				
			||||||
 | 
					    throw new Error('no document 1')
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * @type Doc
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  const doc0 = array0.doc
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * @type Doc
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  const doc1 = array1.doc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  doc0.gc = false
 | 
				
			||||||
 | 
					  doc1.gc = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  array0.insert(0, ['user1item1'])
 | 
				
			||||||
 | 
					  testConnector.syncAll()
 | 
				
			||||||
 | 
					  array1.insert(1, ['user2item1'])
 | 
				
			||||||
 | 
					  testConnector.syncAll()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const snap = snapshot(array0.doc)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  array0.insert(2, ['user1item2'])
 | 
				
			||||||
 | 
					  testConnector.syncAll()
 | 
				
			||||||
 | 
					  array1.insert(3, ['user2item2'])
 | 
				
			||||||
 | 
					  testConnector.syncAll()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const docRestored0 = createDocFromSnapshot(array0.doc, snap)
 | 
				
			||||||
 | 
					  t.compare(docRestored0.getArray('array').toArray(), ['user1item1', 'user2item1'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const docRestored1 = createDocFromSnapshot(array1.doc, snap)
 | 
				
			||||||
 | 
					  t.compare(docRestored1.getArray('array').toArray(), ['user1item1', 'user2item1'])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user