add missing meta apis (mostly contentIds & contentMaps)

This commit is contained in:
Kevin Jahns
2026-01-14 01:49:32 +01:00
parent 2b5138a06d
commit bee0afb38d
7 changed files with 123 additions and 25 deletions

View File

@@ -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<any>}
*
* @private
@@ -596,7 +596,7 @@ export const readIdMap = decoder => {
* @param {Uint8Array} data
* @return {IdMap<any>}
*/
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<Attrs>} idmap
* @param {(attr: ContentAttribute<Attrs>) => boolean} predicate
* @param {(attr: Array<ContentAttribute<Attrs>>) => boolean} predicate
* @return {IdMap<Attrs>}
*/
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 => {

View File

@@ -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<ArrayBuffer>|null} Returns a v2 update containing all deletes that couldn't be applied yet; or null if all deletes were applied successfully.

View File

@@ -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

View File

@@ -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
*/

View File

@@ -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<number,number>} 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

View File

@@ -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<ContentMap>} contents
*/
export const mergeContentMaps = contents => createContentMap(
idmap.mergeIdMaps(contents.map(c => c.inserts)),
idmap.mergeIdMaps(contents.map(c => c.deletes))
)
/**
* @param {Array<ContentIds>} 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<any>} inserts
* @param {import('./IdMap.js').IdMap<any>} 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<any>} 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<any>} buf
*/
export const decodeContentMap = buf => readContentMap(new IdSetDecoderV2(decoding.createDecoder(buf)))
/**
* @todo filter by array of content instead
* @param {ContentMap} contentMap
* @param {(c:Array<idmap.ContentAttribute<any>>)=>boolean} insertPredicate
* @param {(c:Array<idmap.ContentAttribute<any>>)=>boolean} deletePredicate
*/
export const filterContentMap = (contentMap, insertPredicate, deletePredicate) => createContentMap(idmap.filterIdMap(contentMap.inserts, insertPredicate), idmap.filterIdMap(contentMap.deletes, deletePredicate))

View File

@@ -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)