UNPKG

mobx-bonsai-yjs

Version:

Y.js two-way binding for mobx-bonsai

132 lines (113 loc) 3.3 kB
import { action } from "mobx" import { _Dispose, _disposeOnce, getNodeTypeAndKey, getParentToChildPath, NodeWithAnyType, WalkTreeMode, walkTree, } from "mobx-bonsai" import type * as Y from "yjs" import { resolveYjsStructurePath } from "./nodeToYjs/resolveYjsStructurePath" import { setupNodeToYjsReplication } from "./nodeToYjs/setupNodeToYjsReplication" import { createNodeFromYjsObject } from "./yjsToNode/createNodeFromYjsObject" import { setupYjsToNodeReplication } from "./yjsToNode/setupYjsToNodeReplication" import { YjsStructure } from "./yjsTypes/types" /** * Creates a node that is bound to a Y.js data structure. * Y.js Map and Array instances are bound to MobX objects and arrays, respectively. */ export const bindYjsToNode = action( <T extends object>({ yjsDoc, yjsObject, yjsOrigin, }: { /** * The Y.js document. */ yjsDoc: Y.Doc /** * The Y.js data structure to bind. */ yjsObject: YjsStructure /** * The Y.js origin symbol used for binding transactions, or a function that returns the symbol. * One will be automatically generated if not provided. */ yjsOrigin?: symbol | (() => symbol) }): { /** * The bound node. */ node: T /** * Resolves the corresponding Y.js value for a given target node. * * @param node - The node to resolve in the bound Yjs structure. * @returns The resolved Y.js value. * @throws Error if the target node is not found in the bound tree. */ getYjsValueForNode: (node: object) => unknown /** * Disposes the binding. */ dispose: _Dispose /** * Disposes the binding. */ [Symbol.dispose](): void } => { yjsOrigin = yjsOrigin ?? Symbol("mobx-bonsai-yjs-origin") // Convert yjsOrigin to a getter function if it's a plain symbol const yjsOriginGetter = typeof yjsOrigin === "function" ? yjsOrigin : () => yjsOrigin const node = createNodeFromYjsObject<T>(yjsObject) const yjsReplicatingRef = { current: 0 } const yjsOriginCache = new WeakSet<symbol>() const yjsToNodeReplicationAdmin = setupYjsToNodeReplication({ node: node, yjsObject, yjsOriginCache, yjsReplicatingRef, }) const nodeToYjsReplicationAdmin = setupNodeToYjsReplication({ node: node, yjsDoc, yjsObject, yjsOriginGetter, yjsOriginCache, yjsReplicatingRef, }) // run node initialization callbacks here, later, to sync changes walkTree( node, (n) => { const { type } = getNodeTypeAndKey(n) type?._initNode(n as NodeWithAnyType) }, WalkTreeMode.ChildrenFirst ) const ret = { node, getYjsValueForNode: (target: object) => { if (target === node) { return yjsObject } const path = getParentToChildPath(node, target) if (!path) { throw new Error("node not found in the bound tree") } return resolveYjsStructurePath(yjsObject, path) }, dispose: _disposeOnce(() => { nodeToYjsReplicationAdmin.dispose() yjsToNodeReplicationAdmin.dispose() }), [Symbol.dispose]: () => { ret.dispose() }, } return ret } )