draft for WeakLink API
This commit is contained in:
parent
eeae74decf
commit
740598cef2
@ -10,6 +10,7 @@ export {
|
|||||||
YXmlHook as XmlHook,
|
YXmlHook as XmlHook,
|
||||||
YXmlElement as XmlElement,
|
YXmlElement as XmlElement,
|
||||||
YXmlFragment as XmlFragment,
|
YXmlFragment as XmlFragment,
|
||||||
|
WeakLink,
|
||||||
YXmlEvent,
|
YXmlEvent,
|
||||||
YMapEvent,
|
YMapEvent,
|
||||||
YArrayEvent,
|
YArrayEvent,
|
||||||
|
@ -27,6 +27,7 @@ export * from './types/YXmlElement.js'
|
|||||||
export * from './types/YXmlEvent.js'
|
export * from './types/YXmlEvent.js'
|
||||||
export * from './types/YXmlHook.js'
|
export * from './types/YXmlHook.js'
|
||||||
export * from './types/YXmlText.js'
|
export * from './types/YXmlText.js'
|
||||||
|
export * from './types/WeakLink.js'
|
||||||
|
|
||||||
export * from './structs/AbstractStruct.js'
|
export * from './structs/AbstractStruct.js'
|
||||||
export * from './structs/GC.js'
|
export * from './structs/GC.js'
|
||||||
@ -39,5 +40,6 @@ export * from './structs/ContentJSON.js'
|
|||||||
export * from './structs/ContentAny.js'
|
export * from './structs/ContentAny.js'
|
||||||
export * from './structs/ContentString.js'
|
export * from './structs/ContentString.js'
|
||||||
export * from './structs/ContentType.js'
|
export * from './structs/ContentType.js'
|
||||||
|
export * from './structs/ContentLink.js'
|
||||||
export * from './structs/Item.js'
|
export * from './structs/Item.js'
|
||||||
export * from './structs/Skip.js'
|
export * from './structs/Skip.js'
|
||||||
|
99
src/structs/ContentLink.js
Normal file
99
src/structs/ContentLink.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import {
|
||||||
|
UpdateEncoderV1, UpdateEncoderV2, UpdateDecoderV1, UpdateDecoderV2, Transaction, Item, StructStore // eslint-disable-line
|
||||||
|
} from '../internals.js'
|
||||||
|
|
||||||
|
export class ContentLink {
|
||||||
|
/**
|
||||||
|
* @param {Item} item
|
||||||
|
*/
|
||||||
|
constructor (item) {
|
||||||
|
/**
|
||||||
|
* @type {Item}
|
||||||
|
*/
|
||||||
|
this.item = item
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
getLength () {
|
||||||
|
return this.item.length
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {Array<any>}
|
||||||
|
*/
|
||||||
|
getContent () {
|
||||||
|
throw new Error('not implemented')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
isCountable () {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {ContentLink}
|
||||||
|
*/
|
||||||
|
copy () {
|
||||||
|
return new ContentLink(this.item)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} offset
|
||||||
|
* @return {ContentLink}
|
||||||
|
*/
|
||||||
|
splice (offset) {
|
||||||
|
throw new Error('not implemented')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ContentLink} right
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
mergeWith (right) {
|
||||||
|
throw new Error('not implemented')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Transaction} transaction
|
||||||
|
* @param {Item} item
|
||||||
|
*/
|
||||||
|
integrate (transaction, item) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Transaction} transaction
|
||||||
|
*/
|
||||||
|
delete (transaction) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {StructStore} store
|
||||||
|
*/
|
||||||
|
gc (store) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
|
||||||
|
* @param {number} offset
|
||||||
|
*/
|
||||||
|
write (encoder, offset) {
|
||||||
|
throw new Error('not implemented')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
getRef () {
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
|
||||||
|
* @return {ContentLink}
|
||||||
|
*/
|
||||||
|
export const readContentWeakLink = decoder => {
|
||||||
|
throw new Error('not implemented')
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,7 @@ import {
|
|||||||
readContentString,
|
readContentString,
|
||||||
readContentEmbed,
|
readContentEmbed,
|
||||||
readContentDoc,
|
readContentDoc,
|
||||||
|
readContentWeakLink,
|
||||||
createID,
|
createID,
|
||||||
readContentFormat,
|
readContentFormat,
|
||||||
readContentType,
|
readContentType,
|
||||||
@ -719,6 +720,7 @@ export const contentRefs = [
|
|||||||
readContentType, // 7
|
readContentType, // 7
|
||||||
readContentAny, // 8
|
readContentAny, // 8
|
||||||
readContentDoc, // 9
|
readContentDoc, // 9
|
||||||
|
readContentWeakLink, // 10
|
||||||
() => { error.unexpectedCase() } // 10 - Skip is not ItemContent
|
() => { error.unexpectedCase() } // 10 - Skip is not ItemContent
|
||||||
]
|
]
|
||||||
|
|
||||||
|
17
src/types/WeakLink.js
Normal file
17
src/types/WeakLink.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
*
|
||||||
|
* Weak link to another value stored somewhere in the document.
|
||||||
|
*/
|
||||||
|
export class WeakLink {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a reference to an underlying value existing somewhere on in the document.
|
||||||
|
*
|
||||||
|
* @return {T|undefined}
|
||||||
|
*/
|
||||||
|
deref() {
|
||||||
|
throw new Error('not implemented')
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,8 @@ import {
|
|||||||
YArrayRefID,
|
YArrayRefID,
|
||||||
callTypeObservers,
|
callTypeObservers,
|
||||||
transact,
|
transact,
|
||||||
ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line
|
ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item, // eslint-disable-line
|
||||||
|
WeakLink
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
import { typeListSlice } from './AbstractType.js'
|
import { typeListSlice } from './AbstractType.js'
|
||||||
|
|
||||||
@ -201,6 +202,16 @@ export class YArray extends AbstractType {
|
|||||||
return typeListGet(this, index)
|
return typeListGet(this, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the weak link to i-th element from a YArray.
|
||||||
|
*
|
||||||
|
* @param {number} index The index of the element to return from the YArray
|
||||||
|
* @return {WeakLink<T>}
|
||||||
|
*/
|
||||||
|
link(index) {
|
||||||
|
throw new Error('Method not implemented.')
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms this YArray to a JavaScript Array.
|
* Transforms this YArray to a JavaScript Array.
|
||||||
*
|
*
|
||||||
|
@ -14,7 +14,8 @@ import {
|
|||||||
YMapRefID,
|
YMapRefID,
|
||||||
callTypeObservers,
|
callTypeObservers,
|
||||||
transact,
|
transact,
|
||||||
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line
|
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item, // eslint-disable-line
|
||||||
|
WeakLink
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
import * as iterator from 'lib0/iterator'
|
import * as iterator from 'lib0/iterator'
|
||||||
@ -233,6 +234,16 @@ export class YMap extends AbstractType {
|
|||||||
return /** @type {any} */ (typeMapGet(this, key))
|
return /** @type {any} */ (typeMapGet(this, key))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a weak reference link to another element stored in the same document.
|
||||||
|
*
|
||||||
|
* @param {string} key
|
||||||
|
* @return {WeakLink<MapType>|undefined}
|
||||||
|
*/
|
||||||
|
link(key) {
|
||||||
|
throw new Error('Method not implemented.')
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a boolean indicating whether the specified key exists or not.
|
* Returns a boolean indicating whether the specified key exists or not.
|
||||||
*
|
*
|
||||||
|
@ -11,6 +11,7 @@ import * as doc from './doc.tests.js'
|
|||||||
import * as snapshot from './snapshot.tests.js'
|
import * as snapshot from './snapshot.tests.js'
|
||||||
import * as updates from './updates.tests.js'
|
import * as updates from './updates.tests.js'
|
||||||
import * as relativePositions from './relativePositions.tests.js'
|
import * as relativePositions from './relativePositions.tests.js'
|
||||||
|
import * as weakLinks from './weakLinks.tests.js'
|
||||||
|
|
||||||
import { runTests } from 'lib0/testing'
|
import { runTests } from 'lib0/testing'
|
||||||
import { isBrowser, isNode } from 'lib0/environment'
|
import { isBrowser, isNode } from 'lib0/environment'
|
||||||
@ -20,7 +21,7 @@ if (isBrowser) {
|
|||||||
log.createVConsole(document.body)
|
log.createVConsole(document.body)
|
||||||
}
|
}
|
||||||
runTests({
|
runTests({
|
||||||
doc, map, array, text, xml, encoding, undoredo, compatibility, snapshot, updates, relativePositions
|
doc, map, array, text, xml, encoding, undoredo, compatibility, snapshot, updates, relativePositions, weakLinks
|
||||||
}).then(success => {
|
}).then(success => {
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
if (isNode) {
|
if (isNode) {
|
||||||
|
176
tests/weakLinks.tests.js
Normal file
176
tests/weakLinks.tests.js
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
import * as Y from '../src/index.js'
|
||||||
|
import * as t from 'lib0/testing'
|
||||||
|
import { init, compare } from './testHelper.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testBasic = tc => {
|
||||||
|
const doc = new Y.Doc()
|
||||||
|
const map = doc.getMap('map')
|
||||||
|
|
||||||
|
const nested = new Y.Map()
|
||||||
|
nested.set('a1', 'hello')
|
||||||
|
map.set('a', nested)
|
||||||
|
const link = nested.link('a')
|
||||||
|
map.set('b', link)
|
||||||
|
|
||||||
|
const nested2 = map.get('b')
|
||||||
|
t.compare(nested2.toJSON(), nested.toJSON())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testUpdate = tc => {
|
||||||
|
const { testConnector, users, map0, map1 } = init(tc, { users: 2 })
|
||||||
|
map0.set('a', new Y.Map([['a1', 'hello']]))
|
||||||
|
const link0 = /** @type {Y.WeakLink<Y.Map<any>>} */ (map0.link('a'))
|
||||||
|
map0.set('b', link0)
|
||||||
|
|
||||||
|
testConnector.flushAllMessages()
|
||||||
|
const link1 = /** @type {Y.WeakLink<Y.Map<any>>} */ (map1.get('b'))
|
||||||
|
let l1 = /** @type {Y.Map<any>} */ (link1.deref())
|
||||||
|
let l0 = /** @type {Y.Map<any>} */ (link0.deref())
|
||||||
|
t.compare(l1.get('a1'), l0.get('a1'))
|
||||||
|
|
||||||
|
map1.get('a').set('a2', 'world')
|
||||||
|
|
||||||
|
testConnector.flushAllMessages()
|
||||||
|
|
||||||
|
l1 = /** @type {Y.Map<any>} */ (link1.deref())
|
||||||
|
l0 = /** @type {Y.Map<any>} */ (link0.deref())
|
||||||
|
t.compare(l1.get('a2'), l0.get('a2'))
|
||||||
|
|
||||||
|
compare(users)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testDeleteWeakLink = tc => {
|
||||||
|
const { testConnector, users, map0, map1 } = init(tc, { users: 2 })
|
||||||
|
map0.set('a', new Y.Map([['a1', 'hello']]))
|
||||||
|
const link0 = /** @type {Y.WeakLink<Y.Map<any>>} */ (map0.link('a'))
|
||||||
|
map0.set('b', link0)
|
||||||
|
|
||||||
|
testConnector.flushAllMessages()
|
||||||
|
const link1 = /** @type {Y.WeakLink<Y.Map>} */ map1.get('b')
|
||||||
|
const l0 = /** @type {Y.Map<any>} */ (link0.deref())
|
||||||
|
t.compare(link1.ref.get('a1'), l0.get('a1'))
|
||||||
|
|
||||||
|
map1.delete('b') // delete links
|
||||||
|
|
||||||
|
testConnector.flushAllMessages()
|
||||||
|
|
||||||
|
// since links have been deleted, they no longer refer to any content
|
||||||
|
t.compare(link0.deref(), undefined)
|
||||||
|
t.compare(link1.deref(), undefined)
|
||||||
|
|
||||||
|
compare(users)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testDeleteSource = tc => {
|
||||||
|
const { testConnector, users, map0, map1 } = init(tc, { users: 2 })
|
||||||
|
map0.set('a', new Y.Map([['a1', 'hello']]))
|
||||||
|
const link0 = /** @type {Y.WeakLink<Y.Map<any>>} */ (map0.link('a'))
|
||||||
|
map0.set('b', link0)
|
||||||
|
|
||||||
|
testConnector.flushAllMessages()
|
||||||
|
const link1 = /** @type {Y.WeakLink<Y.Map<any>>} */ (map1.get('b'))
|
||||||
|
let l1 = /** @type {Y.Map<any>} */ (link1.deref())
|
||||||
|
let l0 = /** @type {Y.Map<any>} */ (link0.deref())
|
||||||
|
t.compare(l1.get('a1'), l0.get('a1'))
|
||||||
|
|
||||||
|
map1.delete('a') // delete source of the link
|
||||||
|
|
||||||
|
testConnector.flushAllMessages()
|
||||||
|
|
||||||
|
// since source have been deleted, links no longer refer to any content
|
||||||
|
t.compare(link0.deref(), undefined)
|
||||||
|
t.compare(link1.deref(), undefined)
|
||||||
|
|
||||||
|
compare(users)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testObserve = tc => {
|
||||||
|
const doc = new Y.Doc()
|
||||||
|
const map = doc.getMap('map')
|
||||||
|
const array = doc.getArray('array')
|
||||||
|
/**
|
||||||
|
* @type {Array<any>}
|
||||||
|
*/
|
||||||
|
let delta
|
||||||
|
array.observe((e) => delta = e.changes.delta)
|
||||||
|
|
||||||
|
map.set('key', 'value1')
|
||||||
|
const link = map.link('key')
|
||||||
|
array.insert(0, [link])
|
||||||
|
|
||||||
|
delta = []
|
||||||
|
|
||||||
|
map.set('key', 'value2')
|
||||||
|
t.compare(delta, [{ delete: 1 }, { insert: 'value2' }])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testDeepObserve = tc => {
|
||||||
|
const doc = new Y.Doc()
|
||||||
|
const map = doc.getMap('map')
|
||||||
|
const array = doc.getArray('array')
|
||||||
|
/**
|
||||||
|
* @type {Array<any>}
|
||||||
|
*/
|
||||||
|
let events
|
||||||
|
array.observeDeep((e) => events = e)
|
||||||
|
|
||||||
|
const nested = new Y.Map([['key', 'value']])
|
||||||
|
map.set('key', nested)
|
||||||
|
const link = map.link('key')
|
||||||
|
array.insert(0, [link])
|
||||||
|
|
||||||
|
events = []
|
||||||
|
|
||||||
|
nested.set('key', 'value2')
|
||||||
|
for (let i = 0; i < events.length; i++) {
|
||||||
|
let e = events[i]
|
||||||
|
throw new Error('todo')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testObserveRecursive = tc => {
|
||||||
|
const doc = new Y.Doc()
|
||||||
|
const map = doc.getMap('map')
|
||||||
|
const array = doc.getArray('array')
|
||||||
|
/**
|
||||||
|
* @type {any}
|
||||||
|
*/
|
||||||
|
let arrayChanges
|
||||||
|
array.observe((e) => arrayChanges = e.changes)
|
||||||
|
/**
|
||||||
|
* @type {any}
|
||||||
|
*/
|
||||||
|
let mapChanges
|
||||||
|
map.observe((e) => mapChanges = e.changes)
|
||||||
|
|
||||||
|
Y.transact(doc, () => {
|
||||||
|
map.set('key', 'map-value')
|
||||||
|
array.insert(0, [map.link('key')])
|
||||||
|
map.set('key2', array.link(0))
|
||||||
|
})
|
||||||
|
t.compare(arrayChanges.delta, [{ insert: 'map-value' }])
|
||||||
|
t.compare(mapChanges.keys.get('key2'), [{ action: 'insert', oldValue: 'map-value' }])
|
||||||
|
|
||||||
|
t.compare(map.get('key2').deref().deref(), 'map-value')
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user