feat: 收口文档桥接与 OnlyOffice/Sidebar 回归
- 为 documents.save/meta/content、blocks.patch 与 sidebar.dataset.list 补齐 Rust 协议映射、共享契约与桥接执行器 - 对齐 BlockNote、Mindmap、OnlyOffice 的保存/路由元信息,并补真实浏览器回归脚本与 OnlyOffice 部署基线 - 忽略 Rust 本地构建产物与 Harness 调试状态文件,避免临时产物进入仓库历史
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { randomUUID } from "crypto";
|
||||
import type { ConvexHttpClient } from "convex/browser";
|
||||
import { HttpError, requireAuthContext } from "@/lib/auth/authContext";
|
||||
import { apiErrorResponse } from "@/lib/api-utils";
|
||||
import type { PageOptionsState } from "@/types/page-options";
|
||||
@@ -80,6 +81,28 @@ export type QueryEnvelope<T> = {
|
||||
payload: T;
|
||||
};
|
||||
|
||||
export type DocumentBridgeMutationRequest<
|
||||
TArgs extends Record<string, unknown> = Record<string, unknown>,
|
||||
> = {
|
||||
functionName: string;
|
||||
deploymentId: string | null;
|
||||
projectId: string | null;
|
||||
workspaceId: string | null;
|
||||
requestId: string;
|
||||
traceId: string;
|
||||
idempotencyKey: string | null;
|
||||
actorId: string;
|
||||
payloadJson: string;
|
||||
args: TArgs;
|
||||
};
|
||||
|
||||
const DOCUMENT_BRIDGE_MUTATION_FUNCTIONS = {
|
||||
"documents.title.update": "documents:updateTitle",
|
||||
"documents.stats.update": "documents:updateStats",
|
||||
"documents.options.update": "documents:updateOptions",
|
||||
"documents.save": "documents:updateContent",
|
||||
} as const satisfies Record<string, string>;
|
||||
|
||||
function readHeaderValue(headerList: Headers, ...candidates: string[]): string | null {
|
||||
for (const candidate of candidates) {
|
||||
const value = headerList.get(candidate);
|
||||
@@ -187,6 +210,94 @@ export function buildDocumentQueryEnvelope<T>(input: { name: string; payload: T
|
||||
};
|
||||
}
|
||||
|
||||
function getDocumentBridgeMutationFunctionName(commandName: string): string {
|
||||
const functionName =
|
||||
DOCUMENT_BRIDGE_MUTATION_FUNCTIONS[
|
||||
commandName as keyof typeof DOCUMENT_BRIDGE_MUTATION_FUNCTIONS
|
||||
];
|
||||
if (!functionName) {
|
||||
throw new DocumentBridgeError(
|
||||
`未注册文档 bridge mutation: ${commandName}`,
|
||||
500,
|
||||
"TRANSPORT_ERROR",
|
||||
);
|
||||
}
|
||||
return functionName;
|
||||
}
|
||||
|
||||
function buildDocumentCommandPayloadJson(input: {
|
||||
context: BridgeContext;
|
||||
commandName: string;
|
||||
workspaceId: string | null;
|
||||
idempotencyKey: string | null;
|
||||
}): string {
|
||||
return JSON.stringify({
|
||||
kind: "command",
|
||||
name: input.commandName,
|
||||
request_id: input.context.requestId,
|
||||
trace_id: input.context.traceId,
|
||||
deployment_id: input.context.deploymentId,
|
||||
project_id: input.context.projectId,
|
||||
workspace_id: input.workspaceId,
|
||||
tenant_id: input.context.tenantId,
|
||||
idempotency_key: input.idempotencyKey,
|
||||
actor: {
|
||||
type: input.context.actor.actorType,
|
||||
id: input.context.actor.actorId,
|
||||
session_id: input.context.actor.sessionId,
|
||||
},
|
||||
source: {
|
||||
channel: input.context.source.channel,
|
||||
client: input.context.source.client,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function buildDocumentBridgeMutationRequest<
|
||||
TPayload,
|
||||
TArgs extends Record<string, unknown>,
|
||||
>(input: {
|
||||
context: BridgeContext;
|
||||
envelope: CommandEnvelope<TPayload>;
|
||||
mapConvexArgs: (payload: TPayload) => TArgs;
|
||||
}): DocumentBridgeMutationRequest<TArgs> {
|
||||
const workspaceId = input.envelope.target?.workspaceId ?? input.context.workspaceId ?? null;
|
||||
const idempotencyKey = input.envelope.idempotencyKey ?? input.context.idempotencyKey;
|
||||
|
||||
return {
|
||||
functionName: getDocumentBridgeMutationFunctionName(input.envelope.name),
|
||||
deploymentId: input.context.deploymentId,
|
||||
projectId: input.context.projectId,
|
||||
workspaceId,
|
||||
requestId: input.context.requestId,
|
||||
traceId: input.context.traceId,
|
||||
idempotencyKey,
|
||||
actorId: input.context.actor.actorId,
|
||||
payloadJson: buildDocumentCommandPayloadJson({
|
||||
context: input.context,
|
||||
commandName: input.envelope.name,
|
||||
workspaceId,
|
||||
idempotencyKey,
|
||||
}),
|
||||
args: input.mapConvexArgs(input.envelope.payload),
|
||||
};
|
||||
}
|
||||
|
||||
export async function executeDocumentBridgeMutationRequest<
|
||||
TArgs extends Record<string, unknown>,
|
||||
TResult,
|
||||
>(input: {
|
||||
client: ConvexHttpClient;
|
||||
mutation: unknown;
|
||||
request: DocumentBridgeMutationRequest<TArgs>;
|
||||
}): Promise<TResult> {
|
||||
const mutate = input.client.mutation.bind(input.client) as (
|
||||
mutation: unknown,
|
||||
args: TArgs,
|
||||
) => Promise<TResult>;
|
||||
return mutate(input.mutation, input.request.args);
|
||||
}
|
||||
|
||||
export function assertDocumentId(documentId: string | null | undefined): string {
|
||||
const normalized = typeof documentId === "string" ? documentId.trim() : "";
|
||||
if (!normalized) {
|
||||
|
||||
Reference in New Issue
Block a user