diff --git a/README.md b/README.md index eca608ec..2e2b1833 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,8 @@ Sponsorship also comes with special perks! [![Become a Sponsor](https://img.shie collaboratively organize radio broadcasts. * [Cattaz](http://cattaz.io/) A wiki that can run custom applications in the wiki pages. -* [Alldone](https://alldoneapp.com/) A next-gen project management and collaboration platform. +* [Alldone](https://alldoneapp.com/) A next-gen project management and + collaboration platform. ## Table of Contents diff --git a/funding.cjs b/funding.cjs index 66083e5b..aaf7d47f 100644 --- a/funding.cjs +++ b/funding.cjs @@ -7,4 +7,4 @@ log.print( log.GREY, 'The project has grown considerably in the past year. Too much for me to maintain\nin my spare time. Several companies built their products with Yjs.\nYet, this project receives very little funding. Yjs is far from done. I want to\ncreate more awesome extensions and work on the growing number of open issues.\n', log.BOLD, 'Dear user, the future of this project entirely depends on you.\n') log.print(log.BLUE, log.BOLD, 'Please start funding the project now: https://github.com/sponsors/dmonad \n') -log.print(log.GREY, '(This message will be removed when I achieved my funding goal)\n\n') \ No newline at end of file +log.print(log.GREY, '(This message will be removed when I achieved my funding goal)\n\n') diff --git a/src/utils/DeleteSet.js b/src/utils/DeleteSet.js index 60b2af1f..2c50f781 100644 --- a/src/utils/DeleteSet.js +++ b/src/utils/DeleteSet.js @@ -4,7 +4,7 @@ import { getState, splitItem, iterateStructs, - AbstractUpdateDecoder, AbstractDSDecoder, AbstractDSEncoder, DSDecoderV2, DSEncoderV2, Item, GC, StructStore, Transaction, ID // eslint-disable-line + DSDecoderV1, DSEncoderV1, DSDecoderV2, DSEncoderV2, Item, GC, StructStore, Transaction, ID // eslint-disable-line } from '../internals.js' import * as array from 'lib0/array.js' @@ -210,7 +210,7 @@ export const createDeleteSetFromStructStore = ss => { } /** - * @param {AbstractDSEncoder} encoder + * @param {DSEncoderV1 | DSEncoderV2} encoder * @param {DeleteSet} ds * * @private @@ -232,7 +232,7 @@ export const writeDeleteSet = (encoder, ds) => { } /** - * @param {AbstractDSDecoder} decoder + * @param {DSDecoderV1 | DSDecoderV2} decoder * @return {DeleteSet} * * @private @@ -260,7 +260,7 @@ export const readDeleteSet = decoder => { */ /** - * @param {AbstractDSDecoder} decoder + * @param {DSDecoderV1 | DSDecoderV2} decoder * @param {Transaction} transaction * @param {StructStore} store * diff --git a/src/utils/Snapshot.js b/src/utils/Snapshot.js index 263bfa23..102a9c35 100644 --- a/src/utils/Snapshot.js +++ b/src/utils/Snapshot.js @@ -15,7 +15,7 @@ import { findIndexSS, UpdateEncoderV2, applyUpdateV2, - AbstractDSDecoder, AbstractDSEncoder, DSEncoderV2, DSDecoderV1, DSDecoderV2, Transaction, Doc, DeleteSet, Item // eslint-disable-line + AbstractDSDecoder, DSEncoderV2, DSDecoderV1, DSDecoderV2, Transaction, Doc, DeleteSet, Item // eslint-disable-line } from '../internals.js' import * as map from 'lib0/map.js' @@ -78,7 +78,7 @@ export const equalSnapshots = (snap1, snap2) => { /** * @param {Snapshot} snapshot - * @param {AbstractDSEncoder} [encoder] + * @param {DSEncoderV1 | DSEncoderV2} [encoder] * @return {Uint8Array} */ export const encodeSnapshotV2 = (snapshot, encoder = new DSEncoderV2()) => { diff --git a/src/utils/UpdateDecoder.js b/src/utils/UpdateDecoder.js index 372c7749..9f50c7a7 100644 --- a/src/utils/UpdateDecoder.js +++ b/src/utils/UpdateDecoder.js @@ -10,6 +10,9 @@ export class AbstractDSDecoder { * @param {decoding.Decoder} decoder */ constructor (decoder) { + /** + * @type {decoding.Decoder} + */ this.restDecoder = decoder error.methodUnimplemented() } @@ -280,7 +283,7 @@ export class UpdateDecoderV2 extends DSDecoderV2 { * @type {Array} */ this.keys = [] - decoding.readUint8(decoder) // read feature flag - currently unused + decoding.readVarUint(decoder) // read feature flag - currently unused this.keyClockDecoder = new decoding.IntDiffOptRleDecoder(decoding.readVarUint8Array(decoder)) this.clientDecoder = new decoding.UintOptRleDecoder(decoding.readVarUint8Array(decoder)) this.leftClockDecoder = new decoding.IntDiffOptRleDecoder(decoding.readVarUint8Array(decoder)) diff --git a/src/utils/UpdateEncoder.js b/src/utils/UpdateEncoder.js index eb13fdc3..764cf7f0 100644 --- a/src/utils/UpdateEncoder.js +++ b/src/utils/UpdateEncoder.js @@ -6,110 +6,9 @@ import { ID // eslint-disable-line } from '../internals.js' -export class AbstractDSEncoder { - constructor () { - this.restEncoder = encoding.createEncoder() - } - - /** - * @return {Uint8Array} - */ - toUint8Array () { - error.methodUnimplemented() - } - - /** - * Resets the ds value to 0. - * The v2 encoder uses this information to reset the initial diff value. - */ - resetDsCurVal () { } - - /** - * @param {number} clock - */ - writeDsClock (clock) { } - - /** - * @param {number} len - */ - writeDsLen (len) { } -} - -export class AbstractUpdateEncoder extends AbstractDSEncoder { - /** - * @return {Uint8Array} - */ - toUint8Array () { - error.methodUnimplemented() - } - - /** - * @param {ID} id - */ - writeLeftID (id) { } - - /** - * @param {ID} id - */ - writeRightID (id) { } - - /** - * Use writeClient and writeClock instead of writeID if possible. - * @param {number} client - */ - writeClient (client) { } - - /** - * @param {number} info An unsigned 8-bit integer - */ - writeInfo (info) { } - - /** - * @param {string} s - */ - writeString (s) { } - - /** - * @param {boolean} isYKey - */ - writeParentInfo (isYKey) { } - - /** - * @param {number} info An unsigned 8-bit integer - */ - writeTypeRef (info) { } - - /** - * Write len of a struct - well suited for Opt RLE encoder. - * - * @param {number} len - */ - writeLen (len) { } - - /** - * @param {any} any - */ - writeAny (any) { } - - /** - * @param {Uint8Array} buf - */ - writeBuf (buf) { } - - /** - * @param {any} embed - */ - writeJSON (embed) { } - - /** - * @param {string} key - */ - writeKey (key) { } -} - export class DSEncoderV1 { constructor () { - this.restEncoder = new encoding.Encoder() + this.restEncoder = encoding.createEncoder() } toUint8Array () { @@ -228,7 +127,7 @@ export class UpdateEncoderV1 extends DSEncoderV1 { export class DSEncoderV2 { constructor () { - this.restEncoder = new encoding.Encoder() // encodes all the rest / non-optimized + this.restEncoder = encoding.createEncoder() // encodes all the rest / non-optimized this.dsCurrVal = 0 } @@ -288,7 +187,7 @@ export class UpdateEncoderV2 extends DSEncoderV2 { toUint8Array () { const encoder = encoding.createEncoder() - encoding.writeUint8(encoder, 0) // this is a feature flag that we might use in the future + encoding.writeVarUint(encoder, 0) // this is a feature flag that we might use in the future encoding.writeVarUint8Array(encoder, this.keyClockEncoder.toUint8Array()) encoding.writeVarUint8Array(encoder, this.clientEncoder.toUint8Array()) encoding.writeVarUint8Array(encoder, this.leftClockEncoder.toUint8Array()) diff --git a/src/utils/encoding.js b/src/utils/encoding.js index 56cb6836..7a29e7d4 100644 --- a/src/utils/encoding.js +++ b/src/utils/encoding.js @@ -33,7 +33,7 @@ import { DSEncoderV2, DSDecoderV1, DSEncoderV1, - AbstractDSEncoder, AbstractDSDecoder, AbstractUpdateEncoder, AbstractUpdateDecoder, AbstractContent, Doc, Transaction, GC, Item, StructStore, ID // eslint-disable-line + AbstractDSDecoder, AbstractUpdateDecoder, Doc, Transaction, GC, Item, StructStore, ID // eslint-disable-line } from '../internals.js' import * as encoding from 'lib0/encoding.js' @@ -42,7 +42,7 @@ import * as binary from 'lib0/binary.js' import * as map from 'lib0/map.js' /** - * @param {AbstractUpdateEncoder} encoder + * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder * @param {Array} structs All structs by `client` * @param {number} client * @param {number} clock write structs starting with `ID(client,clock)` @@ -65,7 +65,7 @@ const writeStructs = (encoder, structs, client, clock) => { } /** - * @param {AbstractUpdateEncoder} encoder + * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder * @param {StructStore} store * @param {Map} _sm * @@ -317,7 +317,7 @@ export const tryResumePendingDeleteReaders = (transaction, store) => { } /** - * @param {AbstractUpdateEncoder} encoder + * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder * @param {Transaction} transaction * * @private @@ -462,7 +462,7 @@ export const applyUpdate = (ydoc, update, transactionOrigin) => applyUpdateV2(yd * Write all the document as a single update message. If you specify the state of the remote client (`targetStateVector`) it will * only write the operations that are missing. * - * @param {AbstractUpdateEncoder} encoder + * @param {UpdateEncoderV1 | UpdateEncoderV2} encoder * @param {Doc} doc * @param {Map} [targetStateVector] The state of the target that receives the update. Leave empty to write all known structs * @@ -481,7 +481,7 @@ export const writeStateAsUpdate = (encoder, doc, targetStateVector = new Map()) * * @param {Doc} doc * @param {Uint8Array} [encodedTargetStateVector] The state of the target that receives the update. Leave empty to write all known structs - * @param {AbstractUpdateEncoder} [encoder] + * @param {UpdateEncoderV1 | UpdateEncoderV2} [encoder] * @return {Uint8Array} * * @function @@ -546,7 +546,7 @@ export const decodeStateVectorV2 = decodedState => readStateVector(new DSDecoder export const decodeStateVector = decodedState => readStateVector(new DSDecoderV1(decoding.createDecoder(decodedState))) /** - * @param {AbstractDSEncoder} encoder + * @param {DSEncoderV1 | DSEncoderV2} encoder * @param {Map} sv * @function */ @@ -560,7 +560,7 @@ export const writeStateVector = (encoder, sv) => { } /** - * @param {AbstractDSEncoder} encoder + * @param {DSEncoderV1 | DSEncoderV2} encoder * @param {Doc} doc * * @function @@ -571,7 +571,7 @@ export const writeDocumentStateVector = (encoder, doc) => writeStateVector(encod * Encode State as Uint8Array. * * @param {Doc} doc - * @param {AbstractDSEncoder} [encoder] + * @param {DSEncoderV1 | DSEncoderV2} [encoder] * @return {Uint8Array} * * @function diff --git a/src/utils/updates.js b/src/utils/updates.js index ce46fa5d..9794366f 100644 --- a/src/utils/updates.js +++ b/src/utils/updates.js @@ -10,6 +10,8 @@ import { writeDeleteSet, Skip, mergeDeleteSets, + DSEncoderV1, + DSEncoderV2, Item, GC, AbstractUpdateDecoder, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2 // eslint-disable-line } from '../internals.js' @@ -135,6 +137,52 @@ export class LazyStructWriter { */ export const mergeUpdates = updates => mergeUpdatesV2(updates, UpdateDecoderV1, UpdateEncoderV1) +/** + * @param {Uint8Array} update + * @param {typeof DSEncoderV1 | typeof DSEncoderV2} YEncoder + * @param {typeof UpdateDecoderV1 | typeof UpdateDecoderV2} YDecoder + * @return {Uint8Array} + */ +export const encodeStateVectorFromUpdateV2 = (update, YEncoder = DSEncoderV2, YDecoder = UpdateDecoderV2) => { + const encoder = new YEncoder() + const updateDecoder = new LazyStructReader(new YDecoder(decoding.createDecoder(update))) + let curr = updateDecoder.curr + if (curr !== null) { + let size = 1 + let currClient = curr.id.client + let currClock = curr.id.clock + for (; curr !== null; curr = updateDecoder.next()) { + if (currClient !== curr.id.client) { + size++ + // We found a new client + // write what we have to the encoder + encoding.writeVarUint(encoder.restEncoder, currClient) + encoding.writeVarUint(encoder.restEncoder, currClock) + currClient = curr.id.client + } + currClock = curr.id.clock + curr.length + } + // write what we have + encoding.writeVarUint(encoder.restEncoder, currClient) + encoding.writeVarUint(encoder.restEncoder, currClock) + // prepend the size of the state vector + const enc = encoding.createEncoder() + encoding.writeVarUint(enc, size) + encoding.writeBinaryEncoder(enc, encoder.restEncoder) + encoder.restEncoder = enc + return encoder.toUint8Array() + } else { + encoding.writeVarUint(encoder.restEncoder, 0) + return encoder.toUint8Array() + } +} + +/** + * @param {Uint8Array} update + * @return {Uint8Array} + */ +export const encodeStateVectorFromUpdate = update => encodeStateVectorFromUpdateV2(update, DSEncoderV1, UpdateDecoderV2) + /** * 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/y-array.tests.js b/tests/y-array.tests.js index 45022ed6..f2c0e86f 100644 --- a/tests/y-array.tests.js +++ b/tests/y-array.tests.js @@ -64,7 +64,7 @@ export const testInsertThreeElementsTryRegetProperty = tc => { * @param {t.TestCase} tc */ export const testConcurrentInsertWithThreeConflicts = tc => { - var { users, array0, array1, array2 } = init(tc, { users: 3 }) + const { users, array0, array1, array2 } = init(tc, { users: 3 }) array0.insert(0, [0]) array1.insert(0, [1]) array2.insert(0, [2]) @@ -107,7 +107,7 @@ export const testInsertionsInLateSync = tc => { * @param {t.TestCase} tc */ export const testDisconnectReallyPreventsSendingMessages = tc => { - var { testConnector, users, array0, array1 } = init(tc, { users: 3 }) + const { testConnector, users, array0, array1 } = init(tc, { users: 3 }) array0.insert(0, ['x', 'y']) testConnector.flushAllMessages() users[1].disconnect() @@ -388,13 +388,13 @@ const getUniqueNumber = () => _uniqueNumber++ const arrayTransactions = [ function insert (user, gen) { const yarray = user.getArray('array') - var uniqueNumber = getUniqueNumber() - var content = [] - var len = prng.int32(gen, 1, 4) - for (var i = 0; i < len; i++) { + const uniqueNumber = getUniqueNumber() + const content = [] + const len = prng.int32(gen, 1, 4) + for (let i = 0; i < len; i++) { content.push(uniqueNumber) } - var pos = prng.int32(gen, 0, yarray.length) + const pos = prng.int32(gen, 0, yarray.length) const oldContent = yarray.toArray() yarray.insert(pos, content) oldContent.splice(pos, 0, ...content) @@ -402,28 +402,28 @@ const arrayTransactions = [ }, function insertTypeArray (user, gen) { const yarray = user.getArray('array') - var pos = prng.int32(gen, 0, yarray.length) + const pos = prng.int32(gen, 0, yarray.length) yarray.insert(pos, [new Y.Array()]) - var array2 = yarray.get(pos) + const array2 = yarray.get(pos) array2.insert(0, [1, 2, 3, 4]) }, function insertTypeMap (user, gen) { const yarray = user.getArray('array') - var pos = prng.int32(gen, 0, yarray.length) + const pos = prng.int32(gen, 0, yarray.length) yarray.insert(pos, [new Y.Map()]) - var map = yarray.get(pos) + const map = yarray.get(pos) map.set('someprop', 42) map.set('someprop', 43) map.set('someprop', 44) }, function _delete (user, gen) { const yarray = user.getArray('array') - var length = yarray.length + const length = yarray.length if (length > 0) { - var somePos = prng.int32(gen, 0, length - 1) - var delLength = prng.int32(gen, 1, math.min(2, length - somePos)) + let somePos = prng.int32(gen, 0, length - 1) + let delLength = prng.int32(gen, 1, math.min(2, length - somePos)) if (prng.bool(gen)) { - var type = yarray.get(somePos) + const type = yarray.get(somePos) if (type.length > 0) { somePos = prng.int32(gen, 0, type.length - 1) delLength = prng.int32(gen, 0, math.min(2, type.length - somePos)) diff --git a/tests/y-map.tests.js b/tests/y-map.tests.js index 2c3d128a..c1540a76 100644 --- a/tests/y-map.tests.js +++ b/tests/y-map.tests.js @@ -138,7 +138,7 @@ export const testGetAndSetOfMapPropertySyncs = tc => { t.compare(map0.get('stuff'), 'stuffy') testConnector.flushAllMessages() for (const user of users) { - var u = user.getMap('map') + const u = user.getMap('map') t.compare(u.get('stuff'), 'stuffy') } compare(users) @@ -153,7 +153,7 @@ export const testGetAndSetOfMapPropertyWithConflict = tc => { map1.set('stuff', 'c1') testConnector.flushAllMessages() for (const user of users) { - var u = user.getMap('map') + const u = user.getMap('map') t.compare(u.get('stuff'), 'c1') } compare(users) @@ -183,7 +183,7 @@ export const testGetAndSetAndDeleteOfMapProperty = tc => { map1.delete('stuff') testConnector.flushAllMessages() for (const user of users) { - var u = user.getMap('map') + const u = user.getMap('map') t.assert(u.get('stuff') === undefined) } compare(users) @@ -200,7 +200,7 @@ export const testGetAndSetOfMapPropertyWithThreeConflicts = tc => { map2.set('stuff', 'c3') testConnector.flushAllMessages() for (const user of users) { - var u = user.getMap('map') + const u = user.getMap('map') t.compare(u.get('stuff'), 'c3') } compare(users) @@ -223,7 +223,7 @@ export const testGetAndSetAndDeleteOfMapPropertyWithThreeConflicts = tc => { map3.delete('stuff') testConnector.flushAllMessages() for (const user of users) { - var u = user.getMap('map') + const u = user.getMap('map') t.assert(u.get('stuff') === undefined) } compare(users) @@ -296,7 +296,7 @@ export const testObserversUsingObservedeep = tc => { * @param {Object} should */ const compareEvent = (is, should) => { - for (var key in should) { + for (const key in should) { t.compare(should[key], is[key]) } } @@ -474,12 +474,12 @@ export const testYmapEventHasCorrectValueWhenSettingAPrimitiveFromOtherUser = tc const mapTransactions = [ function set (user, gen) { const key = prng.oneOf(gen, ['one', 'two']) - var value = prng.utf16String(gen) + const value = prng.utf16String(gen) user.getMap('map').set(key, value) }, function setType (user, gen) { const key = prng.oneOf(gen, ['one', 'two']) - var type = prng.oneOf(gen, [new Y.Array(), new Y.Map()]) + const type = prng.oneOf(gen, [new Y.Array(), new Y.Map()]) user.getMap('map').set(key, type) if (type instanceof Y.Array) { type.insert(0, [1, 2, 3, 4])