fixed all tests & proper diff /w attribution in nested deltas

This commit is contained in:
Kevin Jahns
2025-12-07 01:21:19 +01:00
parent 718362324a
commit b2df311fce
8 changed files with 87 additions and 29 deletions

8
package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "14.0.0-14",
"license": "MIT",
"dependencies": {
"lib0": "^0.2.115-4"
"lib0": "^0.2.115-6"
},
"devDependencies": {
"@types/node": "^22.14.1",
@@ -3479,9 +3479,9 @@
}
},
"node_modules/lib0": {
"version": "0.2.115-4",
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.115-4.tgz",
"integrity": "sha512-6/oPO1T3Hsl7YAIuCga+iGpIMwY3/6osNCZbMNkBGIdHrhEm+fdW2I4x0UNNL52BvyFTGJslJimj/iOJ+NqABA==",
"version": "0.2.115-6",
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.115-6.tgz",
"integrity": "sha512-Xv2aEEvDYOzWiJEYZy5rM4WpKb1ujJYe70Wds/kDpxEP5wMbS/zWHFnveekx1aG3o8anq8FGrb9CCenGWA1BYg==",
"license": "MIT",
"dependencies": {
"isomorphic.js": "^0.2.4"

View File

@@ -82,7 +82,7 @@
},
"homepage": "https://docs.yjs.dev",
"dependencies": {
"lib0": "^0.2.115-4"
"lib0": "^0.2.115-6"
},
"devDependencies": {
"@types/node": "^22.14.1",

View File

@@ -464,22 +464,22 @@ export class AbstractType {
* @param {import('../utils/IdSet.js').IdSet?} [opts.itemsToRender]
* @param {boolean} [opts.retainInserts] - if true, retain rendered inserts with attributions
* @param {boolean} [opts.retainDeletes] - if true, retain rendered+attributed deletes only
* @param {Set<string>?} [opts.renderAttrs] - set of attrs to render. if null, render all attributes
* @param {boolean} [opts.renderChildren] - if true, retain rendered+attributed deletes only
* @param {import('../utils/IdSet.js').IdSet?} [opts.deletedItems] - used for computing prevItem in attributes
* @param {Set<import('../utils/types.js').YType>|Map<import('../utils/types.js').YType,any>|null} [opts.modified] - set of types that should be rendered as modified children
* @param {Map<import('../utils/types.js').YType,Set<string|null>>|null} [opts.modified] - set of types that should be rendered as modified children
* @param {Deep} [opts.deep] - render child types as delta
* @return {Deep extends true ? ToDeepEventDelta<EventDelta> : EventDelta} The Delta representation of this type.
*
* @public
*/
getContent (am = noAttributionsManager, opts = {}) {
const { itemsToRender = null, retainInserts = false, retainDeletes = false, renderAttrs = null, renderChildren = true, deletedItems = null, modified = null, deep = false } = opts
const { itemsToRender = null, retainInserts = false, retainDeletes = false, deletedItems = null, modified = null, deep = false } = opts
const renderAttrs = modified?.get(this) || null
const renderChildren = (modified == null || opts.modified.get(this)?.has(null))
/**
* @type {EventDelta extends delta.Delta<infer N,infer Attrs,infer Children,infer Text,any> ? delta.DeltaBuilder<N,Attrs,Children,Text,any> : never}
*/
const d = /** @type {any} */ (delta.create(/** @type {any} */ (this).nodeName || null))
typeMapGetDelta(d, /** @type {any} */ (this), renderAttrs, am, deep, modified, deletedItems, itemsToRender)
typeMapGetDelta(d, /** @type {any} */ (this), renderAttrs, am, deep, modified, deletedItems, itemsToRender, opts)
if (renderChildren) {
/**
* @type {delta.FormattingAttributes}
@@ -571,7 +571,7 @@ export class AbstractType {
if (c.deleted ? retainDeletes : retainInserts) {
d.retain(c.content.getLength(), null, attribution ?? {})
} else if (deep && c.content.constructor === ContentType) {
d.insert([/** @type {any} */(c.content).type.getContent(am, { ...opts, renderChildren: true, renderAttrs: null })], null, attribution)
d.insert([/** @type {any} */(c.content).type.getContent(am, opts)], null, attribution)
} else {
d.insert(c.content.getContent(), null, attribution)
}
@@ -1296,6 +1296,8 @@ export const typeMapGetAll = (parent) => {
}
/**
* @todo move this to getContent/getDelta
*
* Render the difference to another ydoc (which can be empty) and highlight the differences with
* attributions.
*
@@ -1305,17 +1307,18 @@ export const typeMapGetAll = (parent) => {
* @template {delta.DeltaBuilder<any,any,any,any>} TypeDelta
* @param {TypeDelta} d
* @param {YType_} parent
* @param {Set<string>?} attrsToRender
* @param {Set<string|null>?} attrsToRender
* @param {import('../internals.js').AbstractAttributionManager} am
* @param {boolean} deep
* @param {Set<import('../utils/types.js').YType>|Map<import('../utils/types.js').YType,any>|null} [modified] - set of types that should be rendered as modified children
* @param {import('../utils/IdSet.js').IdSet?} [deletedItems]
* @param {import('../utils/IdSet.js').IdSet?} [itemsToRender]
* @param {any} [opts]
*
* @private
* @function
*/
export const typeMapGetDelta = (d, parent, attrsToRender, am, deep, modified, deletedItems, itemsToRender) => {
export const typeMapGetDelta = (d, parent, attrsToRender, am, deep, modified, deletedItems, itemsToRender, opts) => {
// @todo support modified ops!
/**
* @param {Item} item
@@ -1334,6 +1337,8 @@ export const typeMapGetDelta = (d, parent, attrsToRender, am, deep, modified, de
if (itemsToRender == null || itemsToRender.hasId(item.lastId)) {
d.unset(key, attribution, c)
}
} else if (deep && c instanceof AbstractType && modified?.has(c)) {
d.update(key, c.getContent(am, opts))
} else {
// find prev content
let prevContentItem = item

View File

@@ -5,7 +5,9 @@ import {
AbstractAttributionManager, Item, AbstractType, Transaction, AbstractStruct // eslint-disable-line
} from '../internals.js'
import * as map from 'lib0/map'
import * as delta from 'lib0/delta' // eslint-disable-line
import * as set from 'lib0/set'
/**
* @typedef {import('./types.js').YType} _YType
@@ -121,8 +123,33 @@ export class YEvent {
*/
getDelta (am = noAttributionsManager, { deep } = {}) {
const itemsToRender = mergeIdSets([diffIdSet(this.transaction.insertSet, this.transaction.deleteSet), diffIdSet(this.transaction.deleteSet, this.transaction.insertSet)])
const modified = deep ? this.transaction.changedParentTypes : null
return /** @type {any} */ (this.target.getContent(am, { itemsToRender, retainDeletes: true, renderAttrs: this.keysChanged, renderChildren: deep || this.childListChanged, deletedItems: this.transaction.deleteSet, deep: !!deep, modified }))
/**
* @todo this should be done only one in the transaction step
*
* @type {Map<import('./types.js').YType,Set<string|null>>|null}
*/
let modified = this.transaction.changed
if (deep) {
// need to add deep changes to copy of modified
const dchanged = new Map()
modified.forEach((attrs, type) => {
dchanged.set(type, new Set(attrs))
})
for (let m of modified.keys()) {
while (m._item != null) {
const item = m._item
const ms = map.setIfUndefined(dchanged, item?.parent, set.create)
if (item && !ms.has(item.parentSub)) {
ms.add(item.parentSub)
m = /** @type {any} */ (item.parent)
} else {
break
}
}
}
modified = dchanged
}
return /** @type {any} */ (this.target.getContent(am, { itemsToRender, retainDeletes: true, deletedItems: this.transaction.deleteSet, deep: !!deep, modified }))
}
/**

View File

@@ -44,7 +44,7 @@ export const diffDocsToDelta = (v1, v2, { am = createAttributionManagerFromDiff(
if (typeConf) {
// @ts-ignore
const shareDelta = type.getContent(am, {
itemsToRender, retainDeletes: true, renderAttrs: /** @type {Set<string>} */ (changedTypes.get(type)), renderChildren: typeConf.has(null), deletedItems: deletesOnly, modified: changedTypes, deep: true
itemsToRender, retainDeletes: true, deletedItems: deletesOnly, modified: changedTypes, deep: true
})
d.update(typename, shareDelta)
}

View File

@@ -35,7 +35,7 @@
"lib0/conditions": "./node_modules/lib0/conditions.js",
"lib0/crypto/jwt": "./node_modules/lib0/crypto/jwt.js",
"lib0/crypto/aes-gcm": "./node_modules/lib0/crypto/aes-gcm.js",
"lib0/delta": "./node_modules/lib0/delta/d2.js",
"lib0/delta": "./node_modules/lib0/delta/delta.js",
"lib0/crypto/ecdsa": "./node_modules/lib0/crypto/ecdsa.js",
"lib0/crypto/rsa-oaep": "./node_modules/lib0/crypto/rsa-oaep.js",
"lib0/hash/rabin": "./node_modules/lib0/hash/rabin.js",
@@ -137,9 +137,7 @@
"lib0/symbol.js": "./node_modules/lib0/symbol.js",
"lib0/dist/symbol.cjs": "./node_modules/lib0/dist/symbol.cjs",
"lib0/symbol": "./node_modules/lib0/symbol.js",
"lib0/traits.js": "./node_modules/lib0/traits.js",
"lib0/dist/traits.cjs": "./node_modules/lib0/dist/traits.cjs",
"lib0/traits": "./node_modules/lib0/traits.js",
"lib0/traits": "./node_modules/lib0/trait/traits.js",
"lib0/testing.js": "./node_modules/lib0/testing.js",
"lib0/dist/testing.cjs": "./node_modules/lib0/dist/testing.cjs",
"lib0/testing": "./node_modules/lib0/testing.js",
@@ -202,7 +200,7 @@
"lib0/conditions": "./node_modules/lib0/conditions.js",
"lib0/crypto/jwt": "./node_modules/lib0/crypto/jwt.js",
"lib0/crypto/aes-gcm": "./node_modules/lib0/crypto/aes-gcm.js",
"lib0/delta": "./node_modules/lib0/delta/d2.js",
"lib0/delta": "./node_modules/lib0/delta/delta.js",
"lib0/crypto/ecdsa": "./node_modules/lib0/crypto/ecdsa.js",
"lib0/crypto/rsa-oaep": "./node_modules/lib0/crypto/rsa-oaep.js",
"lib0/hash/rabin": "./node_modules/lib0/hash/rabin.js",
@@ -304,9 +302,7 @@
"lib0/symbol.js": "./node_modules/lib0/symbol.js",
"lib0/dist/symbol.cjs": "./node_modules/lib0/dist/symbol.cjs",
"lib0/symbol": "./node_modules/lib0/symbol.js",
"lib0/traits.js": "./node_modules/lib0/traits.js",
"lib0/dist/traits.cjs": "./node_modules/lib0/dist/traits.cjs",
"lib0/traits": "./node_modules/lib0/traits.js",
"lib0/traits": "./node_modules/lib0/trait/traits.js",
"lib0/testing.js": "./node_modules/lib0/testing.js",
"lib0/dist/testing.cjs": "./node_modules/lib0/dist/testing.cjs",
"lib0/testing": "./node_modules/lib0/testing.js",

View File

@@ -100,11 +100,10 @@ export const testYdocDiff = () => {
ydocUpdated.getMap('map').get('nested').insert(0, [1])
// @todo add custom attribution
const d = Y.diffDocsToDelta(ydocStart, ydocUpdated)
console.log('calculated diff', d.toJSON())
t.compare(d, delta.create()
.update('text', delta.create().retain(5).insert('world'))
.update('array', delta.create().retain(1).insert(['x']))
.update('map', delta.create().set('newk', 42).update('nested', delta.create().insert([1])))
.update('text', delta.create().retain(5).insert(' world', null, { insert: [] }))
.update('array', delta.create().retain(1).insert(['x'], null, { insert: [] }))
.update('map', delta.create().set('newk', 42, { insert: [] }).update('nested', delta.create().insert([1], null, { insert: [] })))
)
console.log(d.toJSON())
debugger
}

View File

@@ -37,6 +37,37 @@ export const testIterators = _tc => {
console.log(vals, entries, keys)
}
export const testNestedMapEvent = () => {
const ydoc = new Y.Doc()
const ymap = ydoc.getMap()
const ymapNested = ymap.set('nested', new Y.Map())
let called = 0
ymap.observeDeep((events, tr) => {
const event = events.find(event => event.target === ymap) || new Y.YEvent(ymap, tr, new Set())
const d = event.deltaDeep
called++
t.compare(d, delta.create().update('nested', delta.create().set('k', 'v')))
})
ymapNested.set('k', 'v')
t.assert(called === 1)
}
export const testNestedMapEvent2 = () => {
const ydoc = new Y.Doc()
const yarr = ydoc.getArray()
const ymapNested = new Y.Map()
yarr.insert(0, [ymapNested])
let called = 0
yarr.observeDeep((events, tr) => {
const event = events.find(event => event.target === yarr) || new Y.YEvent(yarr, tr, new Set())
const d = event.deltaDeep
called++
t.compare(d, delta.create().modify(delta.create().set('k', 'v')))
})
ymapNested.set('k', 'v')
t.assert(called === 1)
}
/**
* Computing event changes after transaction should result in an error. See yjs#539
*