UNPKG

mobx-bonsai

Version:

A fast lightweight alternative to MobX-State-Tree + Y.js two-way binding

121 lines (104 loc) 3.13 kB
import type * as Y from "yjs" import { setupNodeToYjsReplication } from "./nodeToYjs/setupNodeToYjsReplication" import { createNodeFromYjsObject } from "./yjsToNode/createNodeFromYjsObject" import { setupYjsToNodeReplication } from "./yjsToNode/setupYjsToNodeReplication" import { YjsStructure } from "./yjsTypes/types" import { action } from "mobx" import { walkTree, WalkTreeMode } from "../node/tree/walkTree" import { Dispose, disposeOnce } from "../utils/disposable" import { getParentToChildPath } from "../node/tree/getParentToChildPath" import { resolveYjsStructurePath } from "./nodeToYjs/resolveYjsStructurePath" import { getNodeTypeAndKey, NodeWithAnyType } from "../node/nodeTypeKey/nodeType" /** * 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. * One will be automatically generated if not provided. */ yjsOrigin?: 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") const node = createNodeFromYjsObject<T>(yjsObject) const yjsReplicatingRef = { current: 0 } const yjsToNodeReplicationAdmin = setupYjsToNodeReplication({ node: node, yjsObject, yjsOrigin, yjsReplicatingRef, }) const nodeToYjsReplicationAdmin = setupNodeToYjsReplication({ node: node, yjsDoc, yjsObject, yjsOrigin, 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 } )