diff --git a/src/index.js b/src/index.js index e4ad99e1..5a2893f2 100644 --- a/src/index.js +++ b/src/index.js @@ -76,5 +76,9 @@ export { AbstractConnector, logType, mergeUpdates, - mergeUpdatesV2 + mergeUpdatesV2, + parseUpdateMeta, + parseUpdateMetaV2, + encodeStateVectorFromUpdate, + encodeStateVectorFromUpdateV2 } from './internals.js' diff --git a/src/utils/updates.js b/src/utils/updates.js index dfdd0bf0..7cb4a2fb 100644 --- a/src/utils/updates.js +++ b/src/utils/updates.js @@ -183,6 +183,51 @@ export const encodeStateVectorFromUpdateV2 = (update, YEncoder = DSEncoderV2, YD */ export const encodeStateVectorFromUpdate = update => encodeStateVectorFromUpdateV2(update, DSEncoderV1, UpdateDecoderV2) +/** + * @param {Uint8Array} update + * @param {typeof UpdateDecoderV1 | typeof UpdateDecoderV2} YDecoder + * @return {{ from: Map, to: Map }} + */ +export const parseUpdateMetaV2 = (update, YDecoder = UpdateDecoderV2) => { + /** + * @type {Map} + */ + const from = new Map() + /** + * @type {Map} + */ + const to = new Map() + const updateDecoder = new LazyStructReader(new YDecoder(decoding.createDecoder(update))) + let curr = updateDecoder.curr + if (curr !== null) { + let currClient = curr.id.client + let currClock = curr.id.clock + // write the beginning to `from` + from.set(currClient, currClock) + for (; curr !== null; curr = updateDecoder.next()) { + if (currClient !== curr.id.client) { + // We found a new client + // write the end to `to` + to.set(currClient, currClock) + // write the beginning to `from` + from.set(curr.id.client, curr.id.clock) + // update currClient + currClient = curr.id.client + } + currClock = curr.id.clock + curr.length + } + // write the end to `to` + to.set(currClient, currClock) + } + return { from, to } +} + +/** + * @param {Uint8Array} update + * @return {{ from: Map, to: Map }} + */ +export const parseUpdateMeta = update => parseUpdateMetaV2(update, UpdateDecoderV1) + /** * This method is intended to slice any kind of struct and retrieve the right part. * It does not handle side-effects, so it should only be used by the lazy-encoder. diff --git a/tests/updates.tests.js b/tests/updates.tests.js index d733823c..06d708c0 100644 --- a/tests/updates.tests.js +++ b/tests/updates.tests.js @@ -8,6 +8,9 @@ import * as Y from '../src/index.js' * @property {function(Y.Doc):Uint8Array} Enc.encodeStateAsUpdate * @property {function(Y.Doc, Uint8Array):void} Enc.applyUpdate * @property {function(Uint8Array):void} Enc.logUpdate + * @property {function(Uint8Array):{from:Map,to:Map}} Enc.parseUpdateMeta + * @property {function(Y.Doc):Uint8Array} Enc.encodeStateVector + * @property {function(Uint8Array):Uint8Array} Enc.encodeStateVectorFromUpdate * @property {string} Enc.updateEventName * @property {string} Enc.description */ @@ -20,6 +23,9 @@ const encV1 = { encodeStateAsUpdate: Y.encodeStateAsUpdate, applyUpdate: Y.applyUpdate, logUpdate: Y.logUpdate, + parseUpdateMeta: Y.parseUpdateMeta, + encodeStateVectorFromUpdate: Y.encodeStateVectorFromUpdate, + encodeStateVector: Y.encodeStateVector, updateEventName: 'update', description: 'V1' } @@ -32,6 +38,9 @@ const encV2 = { encodeStateAsUpdate: Y.encodeStateAsUpdateV2, applyUpdate: Y.applyUpdateV2, logUpdate: Y.logUpdateV2, + parseUpdateMeta: Y.parseUpdateMetaV2, + encodeStateVectorFromUpdate: Y.encodeStateVectorFromUpdateV2, + encodeStateVector: Y.encodeStateVectorV2, updateEventName: 'updateV2', description: 'V2' } @@ -50,6 +59,9 @@ const encDoc = { encodeStateAsUpdate: Y.encodeStateAsUpdate, applyUpdate: Y.applyUpdate, logUpdate: Y.logUpdate, + parseUpdateMeta: Y.parseUpdateMetaV2, + encodeStateVectorFromUpdate: Y.encodeStateVectorFromUpdateV2, + encodeStateVector: Y.encodeStateVector, updateEventName: 'update', description: 'Merge via Y.Doc' } @@ -129,6 +141,14 @@ const checkUpdateCases = (ydoc, updates, enc) => { const merged = new Y.Doc() enc.applyUpdate(merged, updates) t.compareArrays(merged.getArray().toArray(), ydoc.getArray().toArray()) + t.compare(enc.encodeStateVector(merged), enc.encodeStateVectorFromUpdate(updates)) + const meta = enc.parseUpdateMeta(updates) + meta.from.forEach((clock, client) => t.assert(clock === 0)) + meta.to.forEach((clock, client) => { + const structs = /** @type {Array} */ (merged.store.clients.get(client)) + const lastStruct = structs[structs.length - 1] + t.assert(lastStruct.id.clock + lastStruct.length === clock) + }) }) }