import { api } from "@/lib/convex/api"; import { getAuthedConvexClient } from "@/lib/convex/route"; import { buildDocumentBridgeContext, buildDocumentCommandEnvelope, buildDocumentQueryEnvelope, documentBridgeErrorResponse, type BridgeContext, } from "@/lib/documents/bridge"; import { recordBridgeCommandFailureArtifacts, recordRustBridgeCommandArtifacts, } from "@/lib/documents/bridge-log"; import { executeRustBridgeMutationTransport, executeRustBridgeQueryTransport, resolveRustBridgeCommandPlan, resolveRustBridgeQueryPlan, } from "@/lib/documents/rust-runtime"; import { extractBlocksFromContent } from "@/lib/document-content"; type BlockLike = { id: string; type?: string; props?: Record; content?: unknown; children?: BlockLike[]; }; function cloneBlock(block: BlockLike): BlockLike { return { ...block, props: block.props ? { ...block.props } : undefined, content: Array.isArray(block.content) ? [...block.content] : block.content, children: Array.isArray(block.children) ? block.children.map((item) => cloneBlock(item)) : block.children, }; } function findBlockInTree( blocks: BlockLike[], blockId: string, ): { block: BlockLike; parent: BlockLike | null; index: number } | null { const stack: Array<{ list: BlockLike[]; parent: BlockLike | null }> = [{ list: blocks, parent: null }]; while (stack.length > 0) { const current = stack.pop(); if (!current) continue; for (let i = 0; i < current.list.length; i += 1) { const block = current.list[i]!; if (block.id === blockId) { return { block, parent: current.parent, index: i }; } if (Array.isArray(block.children) && block.children.length > 0) { stack.push({ list: block.children, parent: block }); } } } return null; } function removeBlockSubtree(blocks: BlockLike[], blockId: string) { const nextTop = blocks.map((item) => cloneBlock(item)); const hit = findBlockInTree(nextTop, blockId); if (!hit) { return { removed: null as BlockLike | null, nextBlocks: nextTop }; } if (hit.parent) { const nextChildren = Array.isArray(hit.parent.children) ? hit.parent.children.map((item) => cloneBlock(item)) : []; const removed = nextChildren.splice(hit.index, 1)[0] ?? null; hit.parent.children = nextChildren; return { removed, nextBlocks: nextTop }; } const removed = nextTop.splice(hit.index, 1)[0] ?? null; return { removed, nextBlocks: nextTop }; } function replaceBlockInTree(blocks: BlockLike[], blockId: string, nextBlock: unknown) { const nextTop = blocks.map((item) => cloneBlock(item)); const hit = findBlockInTree(nextTop, blockId); if (!hit || !nextBlock || typeof nextBlock !== "object" || Array.isArray(nextBlock)) { return { ok: false, nextBlocks: nextTop }; } const normalized = cloneBlock({ ...(nextBlock as BlockLike), id: blockId }); if (hit.parent) { const nextChildren = Array.isArray(hit.parent.children) ? hit.parent.children.map((item) => cloneBlock(item)) : []; nextChildren[hit.index] = normalized; hit.parent.children = nextChildren; return { ok: true, nextBlocks: nextTop }; } nextTop[hit.index] = normalized; return { ok: true, nextBlocks: nextTop }; } async function buildBridgeContext(request: Request, workspaceId: string | null): Promise { return await buildDocumentBridgeContext({ request, workspaceId }); } export async function executeBlockGetBridgeQuery(input: { request: Request; sourceDocumentId: string; blockId: string; }) { const context = await buildBridgeContext(input.request, null); const envelope = buildDocumentQueryEnvelope({ name: "blocks.get", payload: { sourceDocumentId: input.sourceDocumentId, blockId: input.blockId, }, }); const plan = await resolveRustBridgeQueryPlan({ context, envelope }); const { client } = await getAuthedConvexClient(); const doc = await executeRustBridgeQueryTransport<{ content?: unknown } | null>({ client, plan, }); if (!doc) { throw new Error("页面不存在或无权限"); } const blocks = extractBlocksFromContent(doc.content) as BlockLike[]; const hit = findBlockInTree(blocks, input.blockId); if (!hit) { throw new Error("块不存在或无权限"); } return { requestId: context.requestId, traceId: context.traceId, queryName: envelope.name, result: { block: hit.block }, }; } export async function executeBlockPatchBridgeCommand(input: { request: Request; sourceDocumentId: string; workspaceId: string | null; blockId: string; nextBlock: unknown; }) { const context = await buildBridgeContext(input.request, input.workspaceId); const envelope = buildDocumentCommandEnvelope({ name: "blocks.patch", payload: { documentId: input.sourceDocumentId, workspaceId: input.workspaceId, blockId: input.blockId, nextBlock: input.nextBlock, }, context, target: { workspaceId: input.workspaceId, pageId: input.sourceDocumentId, blockId: input.blockId, }, }); const plan = await resolveRustBridgeCommandPlan({ context, envelope }); const { client } = await getAuthedConvexClient(); const doc = await client.query(api.documents.getContent, { id: input.sourceDocumentId }); if (!doc) { throw new Error("页面不存在或无权限"); } const blocks = extractBlocksFromContent(doc.content) as BlockLike[]; const replaced = replaceBlockInTree(blocks, input.blockId, input.nextBlock); if (!replaced.ok) { throw new Error("块不存在或无权限"); } try { const transportResult = await executeRustBridgeMutationTransport({ client, plan, }); await recordRustBridgeCommandArtifacts({ client, context, envelope, plan, result: transportResult, }); } catch (error) { await recordBridgeCommandFailureArtifacts({ client, context, envelope, error, }); throw error; } return { requestId: context.requestId, traceId: context.traceId, commandId: envelope.commandId, commandName: envelope.name, result: { ok: true as const }, }; } export async function executeBlockMoveBridgeCommand(input: { request: Request; sourceDocumentId: string; targetDocumentId: string; blockId: string; }) { const { client } = await getAuthedConvexClient(); const source = await client.query(api.documents.getContent, { id: input.sourceDocumentId }); if (!source) throw new Error("源页面不存在或无权限"); const target = await client.query(api.documents.getContent, { id: input.targetDocumentId }); if (!target) throw new Error("目标页面不存在或无权限"); const sourceBlocks = extractBlocksFromContent(source.content) as BlockLike[]; const removedRes = removeBlockSubtree(sourceBlocks, input.blockId); if (!removedRes.removed) throw new Error("源块不存在或无权限"); const context = await buildBridgeContext(input.request, null); const envelope = buildDocumentCommandEnvelope({ name: "blocks.move", payload: { sourceDocumentId: input.sourceDocumentId, targetDocumentId: input.targetDocumentId, blockId: input.blockId, }, context, target: { pageId: input.targetDocumentId, blockId: input.blockId, }, }); const plan = await resolveRustBridgeCommandPlan({ context, envelope }); try { const transportResult = await executeRustBridgeMutationTransport({ client, plan }); await recordRustBridgeCommandArtifacts({ client, context, envelope, plan, result: transportResult, }); } catch (error) { await recordBridgeCommandFailureArtifacts({ client, context, envelope, error, }); throw error; } return { requestId: context.requestId, traceId: context.traceId, commandId: envelope.commandId, commandName: envelope.name, result: { ok: true as const }, }; } export async function executeBlockEmbedBridgeCommand(input: { request: Request; sourceDocumentId: string; targetDocumentId: string; blockId: string; }) { const { client } = await getAuthedConvexClient(); const source = await client.query(api.documents.getContent, { id: input.sourceDocumentId }); if (!source) throw new Error("源页面不存在或无权限"); const target = await client.query(api.documents.getContent, { id: input.targetDocumentId }); if (!target) throw new Error("目标页面不存在或无权限"); const targetMeta = await client.query(api.documents.getMeta, { id: input.targetDocumentId }); const sourceBlocks = extractBlocksFromContent(source.content) as BlockLike[]; const hit = findBlockInTree(sourceBlocks, input.blockId); if (!hit) throw new Error("源块不存在或无权限"); const anchorId = (targetMeta as { embed_default_block_id?: string | null } | null)?.embed_default_block_id ?? null; const anchorIndex = typeof anchorId === "string" && anchorId.trim() ? extractBlocksFromContent(target.content).findIndex((block) => String((block as BlockLike).id ?? "") === anchorId) : -1; const targetBlocks = extractBlocksFromContent(target.content) as BlockLike[]; void targetBlocks; void anchorIndex; const context = await buildBridgeContext(input.request, null); const envelope = buildDocumentCommandEnvelope({ name: "blocks.embed", payload: { sourceDocumentId: input.sourceDocumentId, targetDocumentId: input.targetDocumentId, blockId: input.blockId, }, context, target: { pageId: input.targetDocumentId, blockId: input.blockId, }, }); const plan = await resolveRustBridgeCommandPlan({ context, envelope }); try { const transportResult = await executeRustBridgeMutationTransport({ client, plan }); await recordRustBridgeCommandArtifacts({ client, context, envelope, plan, result: transportResult, }); } catch (error) { await recordBridgeCommandFailureArtifacts({ client, context, envelope, error, }); throw error; } return { requestId: context.requestId, traceId: context.traceId, commandId: envelope.commandId, commandName: envelope.name, result: { ok: true as const }, }; } export function handleBlockBridgeError(error: unknown) { return documentBridgeErrorResponse(error); }