diff --git a/src/types/YMap.js b/src/types/YMap.js index e2dd7a49..b1417140 100644 --- a/src/types/YMap.js +++ b/src/types/YMap.js @@ -21,12 +21,25 @@ import * as iterator from 'lib0/iterator' /** * @template T + * @typedef {Extract} StringKey + */ + +/** + * This works around some weird JSDoc+TS circular reference issues: https://github.com/microsoft/TypeScript/issues/46369 + * @typedef {boolean|null|string|number|Uint8Array|JsonArray|JsonObject} Json + * @typedef {Json[]} JsonArray + * @typedef {{ [key: string]: Json }} JsonObject + * @typedef {Json|AbstractType} MapValue + */ + +/** + * @template {Record} T * @extends YEvent> * Event that describes the changes on a YMap. */ export class YMapEvent extends YEvent { /** - * @param {YMap} ymap The YArray that changed. + * @param {YMap} ymap The YMap that changed. * @param {Transaction} transaction * @param {Set} subs The keys that changed. */ @@ -37,7 +50,7 @@ export class YMapEvent extends YEvent { } /** - * @template MapType + * @template {Record} MapType * A shared Map implementation. * * @extends AbstractType> @@ -76,7 +89,8 @@ export class YMap extends AbstractType { _integrate (y, item) { super._integrate(y, item) ;/** @type {Map} */ (this._prelimContent).forEach((value, key) => { - this.set(key, value) + // TODO: fix + this.set(/** @type {any} */ (key), value) }) this._prelimContent = null } @@ -97,7 +111,8 @@ export class YMap extends AbstractType { */ const map = new YMap() this.forEach((value, key) => { - map.set(key, value instanceof AbstractType ? /** @type {typeof value} */ (value.clone()) : value) + // TODO: fix + map.set(key, /** @type {any} */ (value instanceof AbstractType ? /** @type {typeof value} */ (value.clone()) : value)) }) return map } @@ -170,12 +185,12 @@ export class YMap extends AbstractType { /** * Executes a provided function on once on every key-value pair. * - * @param {function(MapType,string,YMap):void} f A function to execute on every element of this YArray. + * @param {function(MapType[keyof MapType],StringKey,YMap):void} f A function to execute on every element of this YArray. */ forEach (f) { this._map.forEach((item, key) => { if (!item.deleted) { - f(item.content.getContent()[item.length - 1], key, this) + f(item.content.getContent()[item.length - 1], /** @type {StringKey} */ (key), this) } }) } @@ -192,6 +207,7 @@ export class YMap extends AbstractType { /** * Remove a specified element from this YMap. * + * TODO: type to only allow deletion of optional elements in MapType * @param {string} key The key of the element to remove. */ delete (key) { @@ -205,17 +221,18 @@ export class YMap extends AbstractType { } /** + * @template {StringKey} Key + * @template {MapType[Key]} Value * Adds or updates an element with a specified key and value. - * @template {MapType} VAL * - * @param {string} key The key of the element to add to this YMap - * @param {VAL} value The value of the element to add - * @return {VAL} + * @param {Key} key The key of the element to add to this YMap + * @param {Value} value The value of the element to add + * @return {Value} */ set (key, value) { if (this.doc !== null) { transact(this.doc, transaction => { - typeMapSet(transaction, this, key, /** @type {any} */ (value)) + typeMapSet(transaction, this, key, value) }) } else { /** @type {Map} */ (this._prelimContent).set(key, value) @@ -224,13 +241,14 @@ export class YMap extends AbstractType { } /** + * @template {StringKey} Key * Returns a specified element from this YMap. * - * @param {string} key - * @return {MapType|undefined} + * @param {Key} key + * @return {MapType[Key]|undefined} */ get (key) { - return /** @type {any} */ (typeMapGet(this, key)) + return /** @type {MapType[Key]|undefined} */ (typeMapGet(this, key)) } /** @@ -273,3 +291,9 @@ export class YMap extends AbstractType { * @function */ export const readYMap = _decoder => new YMap() + + +/** @type {YMap<{ foo: { hello: [3, 4, { hiThere: null }] }; }>} */ +const foo = new YMap(); + +foo.has("") diff --git a/tests/undo-redo.tests.js b/tests/undo-redo.tests.js index 1844af86..af7054d4 100644 --- a/tests/undo-redo.tests.js +++ b/tests/undo-redo.tests.js @@ -329,6 +329,7 @@ export const testUndoUntilChangePerformed = _tc => { const yMap = new Y.Map() yMap.set('hello', 'world') yArray.push([yMap]) + /** @type {Y.Map<{ key: string }>} */ const yMap2 = new Y.Map() yMap2.set('key', 'value') yArray.push([yMap2]) @@ -342,7 +343,7 @@ export const testUndoUntilChangePerformed = _tc => { Y.transact(doc2, () => yArray2.delete(0), doc2.clientID) undoManager2.undo() undoManager.undo() - t.compareStrings(yMap2.get('key'), 'value') + t.compareStrings(yMap2.get('key') || '', 'value') } /** diff --git a/tests/y-map.tests.js b/tests/y-map.tests.js index e12d55c9..33221b64 100644 --- a/tests/y-map.tests.js +++ b/tests/y-map.tests.js @@ -19,14 +19,16 @@ export const testMapHavingIterableAsConstructorParamTests = tc => { t.assert(m1.get('number') === 1) t.assert(m1.get('string') === 'hello') + /** @type {Y.Map<{ object: { x: number; }; boolean: boolean; }>} */ const m2 = new Y.Map([ ['object', { x: 1 }], ['boolean', true] ]) map0.set('m2', m2) - t.assert(m2.get('object').x === 1) + t.assert(m2.get('object')?.x === 1) t.assert(m2.get('boolean') === true) + /** @type {Y.Map} */ const m3 = new Y.Map([...m1, ...m2]) map0.set('m3', m3) t.assert(m3.get('number') === 1) @@ -281,7 +283,7 @@ export const testGetAndSetAndDeleteOfMapPropertyWithThreeConflicts = tc => { */ export const testObserveDeepProperties = tc => { const { testConnector, users, map1, map2, map3 } = init(tc, { users: 4 }) - const _map1 = map1.set('map', new Y.Map()) + const _map1 = map1.set('map', /** @type {Y.Map<{ deepmap: Y.Map }>} */ (new Y.Map())) let calls = 0 let dmapid map1.observeDeep(events => { @@ -306,10 +308,10 @@ export const testObserveDeepProperties = tc => { const dmap2 = _map2.get('deepmap') const dmap3 = _map3.get('deepmap') t.assert(calls > 0) - t.assert(compareIDs(dmap1._item.id, dmap2._item.id)) - t.assert(compareIDs(dmap1._item.id, dmap3._item.id)) + t.assert(compareIDs(dmap1?._item?.id || null, dmap2._item.id)) + t.assert(compareIDs(dmap1?._item?.id || null, dmap3._item.id)) // @ts-ignore we want the possibility of dmapid being undefined - t.assert(compareIDs(dmap1._item.id, dmapid)) + t.assert(compareIDs(dmap1?._item?.id || null, dmapid)) compare(users) }