import type { LiveMapDocumentAttributeValue, LiveMapDocumentAttrs, LiveMapDocumentRequestTarget, } from "../../../types/livemap.types.js"; import type { CanonicalPublicAttrs, HsonNode } from "../../../core/types.js"; export type DocumentBoundAttrsMutation = | Readonly<{ kind: "set"; name: string; value: LiveMapDocumentAttributeValue }> | Readonly<{ kind: "setMany"; values: LiveMapDocumentAttrs }> | Readonly<{ kind: "drop"; name: string }> | Readonly<{ kind: "dropMany"; names: readonly string[] }> | Readonly<{ kind: "clear" }> | Readonly<{ kind: "replace"; values: LiveMapDocumentAttrs }> | Readonly<{ kind: "transform"; apply: (current: CanonicalPublicAttrs) => Exclude; }>; export type DocumentBoundTextMutation = | Readonly<{ kind: "set"; value: string | boolean | number | null }> | Readonly<{ kind: "add"; value: string | boolean | number | null }> | Readonly<{ kind: "insert"; index: number; value: string | boolean | number | null }> | Readonly<{ kind: "overwrite"; value: string | boolean | number | null }>; export const LIVETREE_LINKED_IDENTITY_REQUIRED_ERROR_CODE = "LIVETREE_LINKED_IDENTITY_REQUIRED" as const; /** Stable failure for a linked handle whose canonical identity authority is unavailable. */ export class LiveTreeLinkedIdentityRequiredError extends Error { public readonly code = LIVETREE_LINKED_IDENTITY_REQUIRED_ERROR_CODE; public constructor(public readonly operation: string) { super( `LiveTree ${operation} requires canonical identity for this document-bound node; ` + "its active authority binding is unavailable or does not support this context.", ); this.name = "LiveTreeLinkedIdentityRequiredError"; } } export type DocumentBindingNodeRegistration = Readonly<{ owner: object; canonicalTarget: LiveMapDocumentRequestTarget; canonicalPath: readonly number[]; persistedQuid?: string; requireCanonicalIdentity: () => string; delegateAttrs: (mutation: DocumentBoundAttrsMutation) => void; delegateAttrsAsync: (mutation: DocumentBoundAttrsMutation) => Promise; delegateText: (mutation: DocumentBoundTextMutation) => void; delegateTextAsync: (mutation: DocumentBoundTextMutation) => Promise; delegateEmpty: () => void; delegateEmptyAsync: () => Promise; /** True when authority handled removal; false when caller must perform local root teardown. */ delegateRemove: () => boolean; delegateRemoveAsync: () => Promise; rejectStructuralMutation: (operation: string) => never; }>; const DOCUMENT_BINDING_FOR_NODE = new WeakMap(); export function register_document_binding_node( node: HsonNode, registration: DocumentBindingNodeRegistration, ): void { const existing = DOCUMENT_BINDING_FOR_NODE.get(node); if (existing !== undefined && existing.owner !== registration.owner) { throw new Error("Projected LiveTree node already belongs to an active document binding."); } DOCUMENT_BINDING_FOR_NODE.set(node, registration); } export function document_binding_for_node( node: HsonNode, ): DocumentBindingNodeRegistration | undefined { return DOCUMENT_BINDING_FOR_NODE.get(node); } export function unregister_document_binding_node(node: HsonNode, owner: object): void { if (DOCUMENT_BINDING_FOR_NODE.get(node)?.owner === owner) { DOCUMENT_BINDING_FOR_NODE.delete(node); } } /** Reject a public canonical structural write while a projected node is bound. */ export function assert_document_structural_mutation_allowed(node: HsonNode, operation: string): void { document_binding_for_node(node)?.rejectStructuralMutation(operation); } export function delegate_document_text_mutation_if_bound( node: HsonNode, mutation: DocumentBoundTextMutation, ): boolean { const registration = document_binding_for_node(node); if (registration === undefined) return false; registration.delegateText(mutation); return true; } export function delegate_document_empty_if_bound(node: HsonNode): boolean { const registration = document_binding_for_node(node); if (registration === undefined) return false; registration.delegateEmpty(); return true; } export function delegate_document_remove_if_bound(node: HsonNode): boolean { const registration = document_binding_for_node(node); if (registration === undefined) return false; return registration.delegateRemove(); }