/** * YjsDocument – Manages a Y.Doc with shared maps for CAD elements, layers, and blocks. * Provides helpers to sync local state with the CRDT document. */ import * as Y from 'yjs'; import type { CADElement, CADLayer, BlockDefinition } from '../types/cad.types'; export interface YjsDocumentData { elements: Y.Map; layers: Y.Map; blocks: Y.Map; meta: Y.Map; } export class YjsDocument { readonly doc: Y.Doc; readonly data: YjsDocumentData; constructor() { this.doc = new Y.Doc(); this.data = { elements: this.doc.getMap('elements'), layers: this.doc.getMap('layers'), blocks: this.doc.getMap('blocks'), meta: this.doc.getMap('meta'), }; } /** Get all elements as a plain array */ getElements(): CADElement[] { return Array.from(this.data.elements.values()); } /** Get all layers as a plain array */ getLayers(): CADLayer[] { return Array.from(this.data.layers.values()); } /** Get all blocks as a plain array */ getBlocks(): BlockDefinition[] { return Array.from(this.data.blocks.values()); } /** Add or update a single element */ setElement(el: CADElement): void { this.doc.transact(() => { this.data.elements.set(el.id, el); }); } /** Remove an element by id */ deleteElement(id: string): void { this.data.elements.delete(id); } /** Add or update a layer */ setLayer(layer: CADLayer): void { this.data.layers.set(layer.id, layer); } /** Remove a layer by id */ deleteLayer(id: string): void { this.data.layers.delete(id); } /** Add or update a block definition */ setBlock(block: BlockDefinition): void { this.data.blocks.set(block.id, block); } /** Remove a block by id */ deleteBlock(id: string): void { this.data.blocks.delete(id); } /** Bulk-load local state into the Y.Doc (replaces all content) */ loadFromState(state: { elements: CADElement[]; layers: CADLayer[]; blocks: BlockDefinition[]; }): void { this.doc.transact(() => { this.data.elements.clear(); for (const el of state.elements) { this.data.elements.set(el.id, el); } this.data.layers.clear(); for (const layer of state.layers) { this.data.layers.set(layer.id, layer); } this.data.blocks.clear(); for (const block of state.blocks) { this.data.blocks.set(block.id, block); } }); } /** Export current state as a plain object */ toState(): { elements: CADElement[]; layers: CADLayer[]; blocks: BlockDefinition[]; } { return { elements: this.getElements(), layers: this.getLayers(), blocks: this.getBlocks(), }; } /** Encode full document state as update binary */ encodeState(): Uint8Array { return Y.encodeStateAsUpdate(this.doc); } /** Apply a remote update binary */ applyUpdate(update: Uint8Array): void { Y.applyUpdate(this.doc, update); } /** Register a callback for document changes */ onChange(callback: () => void): () => void { this.doc.on('update', callback); return () => this.doc.off('update', callback); } /** Destroy the document */ destroy(): void { this.doc.destroy(); } }