diff --git a/src/utils/IdMap.js b/src/utils/IdMap.js index 8fbb3fd8..30f848b7 100644 --- a/src/utils/IdMap.js +++ b/src/utils/IdMap.js @@ -3,7 +3,7 @@ import { findIndexInIdRanges, findRangeStartInIdRanges, _deleteRangeFromIdSet, - DSDecoderV1, DSDecoderV2, IdSetEncoderV1, IdSetEncoderV2, IdSet, ID, // eslint-disable-line + IdSetDecoderV1, IdSetDecoderV2, IdSetEncoderV1, IdSetEncoderV2, IdSet, ID, // eslint-disable-line _insertIntoIdSet, _intersectSets, createIdSet, @@ -534,7 +534,7 @@ export const encodeIdMap = idmap => { } /** - * @param {DSDecoderV1 | DSDecoderV2} decoder + * @param {IdSetDecoderV1 | IdSetDecoderV2} decoder * @return {IdMap} * * @private @@ -596,7 +596,7 @@ export const readIdMap = decoder => { * @param {Uint8Array} data * @return {IdMap} */ -export const decodeIdMap = data => readIdMap(new DSDecoderV2(decoding.createDecoder(data))) +export const decodeIdMap = data => readIdMap(new IdSetDecoderV2(decoding.createDecoder(data))) /** * @template Attrs @@ -645,7 +645,7 @@ export const intersectMaps = _intersectSets * * @template Attrs * @param {IdMap} idmap - * @param {(attr: ContentAttribute) => boolean} predicate + * @param {(attr: Array>) => boolean} predicate * @return {IdMap} */ export const filterIdMap = (idmap, predicate) => { @@ -656,7 +656,7 @@ export const filterIdMap = (idmap, predicate) => { */ const attrRanges = [] ranges.getIds().forEach((range) => { - if (range.attrs.some(predicate)) { + if (predicate(range.attrs)) { const rangeCpy = range.copyWith(range.clock, range.len) attrRanges.push(rangeCpy) rangeCpy.attrs.forEach(attr => { diff --git a/src/utils/IdSet.js b/src/utils/IdSet.js index 8b42c6b5..7e73b87e 100644 --- a/src/utils/IdSet.js +++ b/src/utils/IdSet.js @@ -7,7 +7,7 @@ import { IdMap, AttrRanges, AttrRange, - Skip, AbstractStruct, DSDecoderV1, IdSetEncoderV1, DSDecoderV2, IdSetEncoderV2, Item, GC, StructStore, Transaction, ID // eslint-disable-line + Skip, AbstractStruct, IdSetDecoderV1, IdSetEncoderV1, IdSetDecoderV2, IdSetEncoderV2, Item, GC, StructStore, Transaction, ID // eslint-disable-line } from '../internals.js' import * as array from 'lib0/array' @@ -703,7 +703,7 @@ export const writeIdSet = (encoder, idSet) => { } /** - * @param {DSDecoderV1 | DSDecoderV2} decoder + * @param {IdSetDecoderV1 | IdSetDecoderV2} decoder * @return {IdSet} * * @private @@ -735,7 +735,7 @@ export const readIdSet = decoder => { */ /** - * @param {DSDecoderV1 | DSDecoderV2} decoder + * @param {IdSetDecoderV1 | IdSetDecoderV2} decoder * @param {Transaction} transaction * @param {StructStore} store * @return {Uint8Array|null} Returns a v2 update containing all deletes that couldn't be applied yet; or null if all deletes were applied successfully. diff --git a/src/utils/Snapshot.js b/src/utils/Snapshot.js index c585a9a2..0f57a2fd 100644 --- a/src/utils/Snapshot.js +++ b/src/utils/Snapshot.js @@ -15,7 +15,7 @@ import { applyUpdateV2, LazyStructReader, equalIdSets, - UpdateDecoderV1, UpdateDecoderV2, IdSetEncoderV1, IdSetEncoderV2, DSDecoderV1, DSDecoderV2, Transaction, Doc, IdSet, Item, // eslint-disable-line + UpdateDecoderV1, UpdateDecoderV2, IdSetEncoderV1, IdSetEncoderV2, IdSetDecoderV1, IdSetDecoderV2, Transaction, Doc, IdSet, Item, // eslint-disable-line mergeIdSets } from '../internals.js' @@ -80,10 +80,10 @@ export const encodeSnapshot = snapshot => encodeSnapshotV2(snapshot, new IdSetEn /** * @param {Uint8Array} buf - * @param {DSDecoderV1 | DSDecoderV2} [decoder] + * @param {IdSetDecoderV1 | IdSetDecoderV2} [decoder] * @return {Snapshot} */ -export const decodeSnapshotV2 = (buf, decoder = new DSDecoderV2(decoding.createDecoder(buf))) => { +export const decodeSnapshotV2 = (buf, decoder = new IdSetDecoderV2(decoding.createDecoder(buf))) => { return new Snapshot(readIdSet(decoder), readStateVector(decoder)) } @@ -91,7 +91,7 @@ export const decodeSnapshotV2 = (buf, decoder = new DSDecoderV2(decoding.createD * @param {Uint8Array} buf * @return {Snapshot} */ -export const decodeSnapshot = buf => decodeSnapshotV2(buf, new DSDecoderV1(decoding.createDecoder(buf))) +export const decodeSnapshot = buf => decodeSnapshotV2(buf, new IdSetDecoderV1(decoding.createDecoder(buf))) /** * @param {IdSet} ds diff --git a/src/utils/UpdateDecoder.js b/src/utils/UpdateDecoder.js index 67a38ceb..c269e6ac 100644 --- a/src/utils/UpdateDecoder.js +++ b/src/utils/UpdateDecoder.js @@ -4,7 +4,11 @@ import { ID, createID } from '../internals.js' -export class DSDecoderV1 { +/** + * @typedef {IdSetDecoderV1 | IdSetDecoderV2} IdSetDecoder + */ + +export class IdSetDecoderV1 { /** * @param {decoding.Decoder} decoder */ @@ -31,7 +35,7 @@ export class DSDecoderV1 { } } -export class UpdateDecoderV1 extends DSDecoderV1 { +export class UpdateDecoderV1 extends IdSetDecoderV1 { /** * @return {ID} */ @@ -122,7 +126,7 @@ export class UpdateDecoderV1 extends DSDecoderV1 { } } -export class DSDecoderV2 { +export class IdSetDecoderV2 { /** * @param {decoding.Decoder} decoder */ @@ -156,7 +160,7 @@ export class DSDecoderV2 { } } -export class UpdateDecoderV2 extends DSDecoderV2 { +export class UpdateDecoderV2 extends IdSetDecoderV2 { /** * @param {decoding.Decoder} decoder */ diff --git a/src/utils/encoding.js b/src/utils/encoding.js index 9d1d951a..fe409a7b 100644 --- a/src/utils/encoding.js +++ b/src/utils/encoding.js @@ -26,7 +26,7 @@ import { UpdateEncoderV1, UpdateEncoderV2, IdSetEncoderV2, - DSDecoderV1, + IdSetDecoderV1, IdSetEncoderV1, mergeUpdates, mergeUpdatesV2, @@ -36,7 +36,7 @@ import { readStructSet, removeRangesFromStructSet, createIdSet, - StructSet, IdSet, DSDecoderV2, Doc, Transaction, GC, Item, StructStore, // eslint-disable-line + StructSet, IdSet, IdSetDecoderV2, Doc, Transaction, GC, Item, StructStore, // eslint-disable-line createID, IdRange } from '../internals.js' @@ -536,7 +536,7 @@ export const encodeStateAsUpdate = (doc, encodedTargetStateVector) => encodeStat /** * Read state vector from Decoder and return as Map * - * @param {DSDecoderV1 | DSDecoderV2} decoder + * @param {IdSetDecoderV1 | IdSetDecoderV2} decoder * @return {Map} Maps `client` to the number next expected `clock` from that client. * * @function @@ -570,7 +570,7 @@ export const readStateVector = decoder => { * * @function */ -export const decodeStateVector = decodedState => readStateVector(new DSDecoderV1(decoding.createDecoder(decodedState))) +export const decodeStateVector = decodedState => readStateVector(new IdSetDecoderV1(decoding.createDecoder(decodedState))) /** * @param {IdSetEncoderV1 | IdSetEncoderV2} encoder diff --git a/src/utils/meta.js b/src/utils/meta.js index 420183c0..e41b2dab 100644 --- a/src/utils/meta.js +++ b/src/utils/meta.js @@ -5,6 +5,8 @@ import * as idmap from './IdMap.js' import * as idset from './IdSet.js' import { IdSetEncoderV2 } from './UpdateEncoder.js' +import { IdSetDecoderV2 } from './UpdateDecoder.js' +import * as decoding from 'lib0/decoding' /** * @typedef {{ inserts: import('./IdSet.js').IdSet, deletes: import('./IdSet.js').IdSet }} ContentIds @@ -18,7 +20,7 @@ import { IdSetEncoderV2 } from './UpdateEncoder.js' * @param {import('./IdSet.js').IdSet} inserts * @param {import('./IdSet.js').IdSet} deletes */ -export const createContentIds = (inserts, deletes) => ({ inserts, deletes }) +export const createContentIds = (inserts = idset.createIdSet(), deletes = idset.createIdSet()) => ({ inserts, deletes }) /** * @param {ContentMap} contentMap @@ -50,6 +52,31 @@ export const createContentIdsFromDocDiff = (ydocPrev, ydocNext) => export const excludeContentIds = (content, excludeContent) => createContentIds(idset.diffIdSet(content.inserts, excludeContent.inserts), idset.diffIdSet(content.deletes, excludeContent.deletes)) +/** + * @param {ContentMap} content + * @param {ContentIds | ContentMap} excludeContent + */ +export const excludeContentMaps = (content, excludeContent) => createContentMap( + idmap.diffIdMap(content.inserts, excludeContent.inserts), + idmap.diffIdMap(content.deletes, excludeContent.deletes) +) + +/** + * @param {Array} contents + */ +export const mergeContentMaps = contents => createContentMap( + idmap.mergeIdMaps(contents.map(c => c.inserts)), + idmap.mergeIdMaps(contents.map(c => c.deletes)) +) + +/** + * @param {Array} contents + */ +export const mergeContentIds = contents => createContentIds( + idset.mergeIdSets(contents.map(c => c.inserts)), + idset.mergeIdSets(contents.map(c => c.deletes)) +) + /** * @param {import('./IdMap.js').IdMap} inserts * @param {import('./IdMap.js').IdMap} deletes @@ -78,7 +105,27 @@ export const writeContentIds = (encoder, contentIds) => { /** * @param {ContentIds} contentIds */ -export const encodeContentIds = contentIds => writeContentIds(new IdSetEncoderV2(), contentIds) +export const encodeContentIds = contentIds => { + const encoder = new IdSetEncoderV2() + writeContentIds(encoder, contentIds) + return encoder.toUint8Array() +} + +/** + * @todo this encoding needs to be heavily optimized for production + * + * @param {import('./UpdateDecoder.js').IdSetDecoder} decoder + * @return {ContentIds} + */ +export const readContentIds = decoder => createContentIds( + idset.readIdSet(decoder), + idset.readIdSet(decoder) +) + +/** + * @param {Uint8Array} buf + */ +export const decodeContentIds = buf => readContentIds(new IdSetDecoderV2(decoding.createDecoder(buf))) /** * @todo this encoding needs to be heavily optimized for production @@ -91,7 +138,54 @@ export const writeContentMap = (encoder, contentMap) => { idmap.writeIdMap(encoder, contentMap.deletes) } +/** + * @todo this encoding needs to be heavily optimized for production + * + * @param {import('./UpdateDecoder.js').IdSetDecoder} decoder + * @return {ContentMap} contentMap + */ +export const readContentMap = (decoder) => createContentMap( + idmap.readIdMap(decoder), + idmap.readIdMap(decoder) +) + /** * @param {ContentMap} contentMap */ -export const encodeContentMap = contentMap => writeContentMap(new IdSetEncoderV2(), contentMap) +export const encodeContentMap = contentMap => { + const encoder = new IdSetEncoderV2() + writeContentMap(encoder, contentMap) + return encoder.toUint8Array() +} + +/** + * @param {ContentMap} mapA + * @param {ContentMap|ContentIds} mapB + */ +export const intersectContentMap = (mapA, mapB) => createContentMap( + idmap.intersectMaps(mapA.inserts, mapB.inserts), + idmap.intersectMaps(mapA.deletes, mapB.deletes) +) + +/** + * @param {ContentIds} setA + * @param {ContentIds|ContentMap} setB + */ +export const intersectContentIds = (setA, setB) => createContentIds( + idset.intersectSets(setA.inserts, setB.inserts), + idset.intersectSets(setA.deletes, setB.deletes) +) + +/** + * @param {Uint8Array} buf + */ +export const decodeContentMap = buf => readContentMap(new IdSetDecoderV2(decoding.createDecoder(buf))) + +/** + * @todo filter by array of content instead + * @param {ContentMap} contentMap + * @param {(c:Array>)=>boolean} insertPredicate + * @param {(c:Array>)=>boolean} deletePredicate + */ +export const filterContentMap = (contentMap, insertPredicate, deletePredicate) => createContentMap(idmap.filterIdMap(contentMap.inserts, insertPredicate), idmap.filterIdMap(contentMap.deletes, deletePredicate)) + diff --git a/tests/attribution.tests.js b/tests/attribution.tests.js index c46e1a3e..4b48c071 100644 --- a/tests/attribution.tests.js +++ b/tests/attribution.tests.js @@ -160,8 +160,8 @@ export const testAttributionSession1 = tc => { t.compare(d2, delta.create().insert('a').insert('b', null, { delete: ['0'] }).insert('c', null, { insert: ['1'] })) const onlyUser0ChangesAttributed = { - inserts: Y.filterIdMap(globalAttributions.inserts, attr => attr.name === 'insert' && attr.val === '0'), - deletes: Y.filterIdMap(globalAttributions.deletes, attr => attr.name === 'delete' && attr.val === '0') + inserts: Y.filterIdMap(globalAttributions.inserts, attrs => attrs.some(attr => attr.name === 'insert' && attr.val === '0')), + deletes: Y.filterIdMap(globalAttributions.deletes, attrs => attrs.some(attr => attr.name === 'delete' && attr.val === '0')) } const amUser0 = new Y.TwosetAttributionManager(onlyUser0ChangesAttributed.inserts, onlyUser0ChangesAttributed.deletes) const d3 = text0.toDelta(amUser0)