import { createSimpleMindMapBridge, type MindmapAdapterProjection, type SimpleMindMapBridge, type SimpleMindMapBridgeEvent, type SimpleMindMapPluginName, } from "./simple-mind-map-bridge"; import type { MindmapCompatPayloadPatch, MindmapKernelCommand } from "./mindmap-command-diff"; export type { MindmapAdapterProjection, SimpleMindMapBridge, SimpleMindMapBridgeEvent, } from "./simple-mind-map-bridge"; export type LeptosMindmapAdapterOptions = { el: HTMLElement; projection: MindmapAdapterProjection; mode?: "edit" | "readonly"; pluginNames?: SimpleMindMapPluginName[]; runtimeOptions?: Record; onEvent?: (event: SimpleMindMapBridgeEvent) => void; }; export type RequestMindmapAdapterProjectionInput = { documentId: string; mindmapId: string; endpoint?: string; fetcher?: typeof fetch; }; export type MindmapCommandApplyInput = { documentId: string; mindmapId: string; commands: Array; projectionRevision?: number | null; endpoint?: string; fetcher?: typeof fetch; }; export type MindmapCommandApplyResult = { ok: true; kernelRevision: number | null; raw: unknown; }; export class MindmapCommandBridgeError extends Error { readonly code: "command_failed"; readonly status: number | null; constructor(message: string, status: number | null = null) { super(message); this.name = "MindmapCommandBridgeError"; this.code = "command_failed"; this.status = status; } } const isRecord = (value: unknown): value is Record => typeof value === "object" && value !== null && !Array.isArray(value); export const isMindmapAdapterProjection = (value: unknown): value is MindmapAdapterProjection => { if (!isRecord(value)) return false; return ( value.schema === "mnote.mindmap.simple_mind_map_scene.v1" && value.runtime === "simple-mind-map" && "root" in value && typeof value.kernelRevision === "number" ); }; export const createMindmapAdapterProjectionEndpoint = (documentId: string, mindmapId: string) => { const doc = encodeURIComponent(documentId); const map = encodeURIComponent(mindmapId); return `/api/mindmap/${doc}/${map}?view=simple_mind_map_scene&queryName=mindmap.simple_mind_map_scene.get`; }; export const createMindmapCommandApplyEndpoint = (documentId: string, mindmapId: string) => { const doc = encodeURIComponent(documentId); const map = encodeURIComponent(mindmapId); return `/api/mindmap/${doc}/${map}`; }; export const requestMindmapAdapterProjection = async ( input: RequestMindmapAdapterProjectionInput, ): Promise => { const fetcher = input.fetcher ?? fetch; const endpoint = input.endpoint ?? createMindmapAdapterProjectionEndpoint(input.documentId, input.mindmapId); const response = await fetcher(endpoint, { headers: { Accept: "application/json" }, }); if (!response.ok) { throw new Error(`projection_load_failed:${response.status}`); } const payload = (await response.json()) as unknown; const projection = isRecord(payload) && "result" in payload ? payload.result : payload; if (!isMindmapAdapterProjection(projection)) { throw new Error("projection_load_failed:invalid_adapter_projection"); } return projection; }; const readKernelRevision = (value: unknown): number | null => { if (!isRecord(value)) return null; const candidates = [ value.kernelRevision, value.projectionRevision, isRecord(value.result) ? value.result.kernelRevision : null, isRecord(value.result) ? value.result.projectionRevision : null, ]; for (const candidate of candidates) { if (typeof candidate === "number" && Number.isFinite(candidate)) return candidate; } return null; }; export const executeMindmapCommandApply = async ( input: MindmapCommandApplyInput, ): Promise => { if (input.commands.length === 0) { throw new MindmapCommandBridgeError("command_failed:empty_commands"); } const fetcher = input.fetcher ?? fetch; const endpoint = input.endpoint ?? createMindmapCommandApplyEndpoint(input.documentId, input.mindmapId); const response = await fetcher(endpoint, { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json", }, body: JSON.stringify({ commandName: "mindmap.command.apply", documentId: input.documentId, mindmapId: input.mindmapId, commands: input.commands, projectionRevision: input.projectionRevision ?? null, }), }); const raw = (await response.json().catch(() => null)) as unknown; if (!response.ok) { throw new MindmapCommandBridgeError(`command_failed:${response.status}`, response.status); } return { ok: true, kernelRevision: readKernelRevision(raw), raw, }; }; export const executeMindmapCommandApplyAndRefreshProjection = async ( input: MindmapCommandApplyInput & { projectionEndpoint?: string }, ): Promise => { await executeMindmapCommandApply(input); return requestMindmapAdapterProjection({ documentId: input.documentId, mindmapId: input.mindmapId, endpoint: input.projectionEndpoint, fetcher: input.fetcher, }); }; export const createLeptosMindmapAdapter = async ( input: LeptosMindmapAdapterOptions, ): Promise => { if (!isMindmapAdapterProjection(input.projection)) { throw new Error("adapter_init_failed:invalid_projection"); } return createSimpleMindMapBridge(input); };