import { getAuthedConvexClient } from "@/lib/convex/route";
import {
  type CommandEnvelope,
  type BridgeContext,
} from "@/lib/documents/bridge";
import {
  recordBridgeCommandFailureArtifacts,
  recordRustBridgeCommandArtifacts,
} from "@/lib/documents/bridge-log";
import {
  executeRustBridgeMutationTransport,
  resolveRustBridgeCommandPlan,
} from "@/lib/documents/rust-runtime";
import type { PageOptionsState } from "@/types/page-options";

export type DocumentTitleUpdatePayload = {
  documentId: string;
  workspaceId: string | null;
  title: string;
};

export type DocumentStatsUpdatePayload = {
  documentId: string;
  workspaceId: string | null;
  stats: {
    wordCount: number;
    characterCount: number;
    blockCount: number;
    todoTotal: number;
    todoDone: number;
  };
};

export type DocumentOptionsUpdatePayload = {
  documentId: string;
  workspaceId: string | null;
  options: Partial<PageOptionsState>;
};

export type MetadataCommandExecutionResult = {
  requestId: string;
  traceId: string;
  commandId: string;
  commandName: string;
};

export async function executeMetadataBridgeCommand<TPayload>(input: {
  context: BridgeContext;
  envelope: CommandEnvelope<TPayload>;
}): Promise<MetadataCommandExecutionResult> {
  const { client } = await getAuthedConvexClient();
  try {
    const plan = await resolveRustBridgeCommandPlan({
      context: input.context,
      envelope: input.envelope,
    });
    const transportResult = await executeRustBridgeMutationTransport({
      client,
      plan,
    });


envelope: CommandEnvelope<TPayload>;
}): Promise<MetadataCommandExecutionResult> {
  const { client } = await getAuthedConvexClient();
  try {
    const plan = await resolveRustBridgeCommandPlan({
      context: input.context,
      envelope: input.envelope,
    });
    const transportResult = await executeRustBridgeMutationTransport({
      client,
      plan,
    });
    await recordRustBridgeCommandArtifacts({
      client,
      context: input.context,
      envelope: input.envelope,
      plan,
      result: transportResult,
    });
  } catch (error) {
    await recordBridgeCommandFailureArtifacts({
      context: input.context,
      envelope: input.envelope,
      client,
      error,
    });
    throw error;
  }

  return {
    requestId: input.context.requestId,
    traceId: input.context.traceId,
    commandId: input.envelope.commandId,
    commandName: input.envelope.name,
  };
}


import { describe, expect, it } from "vitest";
import { buildPageAggregateFromDocumentPayloads } from "@/lib/documents/page-aggregate-builder";

describe("page-aggregate-builder", () => {
  it("会把 documents meta 与 content 收口为统一 page aggregate", () => {
    const aggregate = buildPageAggregateFromDocumentPayloads({
      meta: {
        id: "doc_1",
        workspace_id: "ws_1",
        title: " 页面标题 ",
        updated_at: "2026-04-22T12:00:00.000Z",
        can_edit: false,
        disable_download: true,
        disable_copy: false,
        wide_layout: true,
        use_small_text: true,
        show_heading_numbers: false,
        show_toc: true,
        show_structure: false,
        protect_editing: false,
        show_word_count: true,
        collapse_backlinks: true,
        page_font: "default",
        layout_density: "compact",
        hide_child_pages: true,
        show_block_ref_count: true,
        embed_default_block_id: "block_1",
        word_count: 20,
        character_count: 40,
        block_count: 2,
        todo_total: 3,
        todo_done: 1,
      },
      contentPayload: {
        content: [{ id: "block_1", type: "paragraph", content: [] }],
        revision: 8,
        conflict_detection_key: "doc_1:8",
        page_subtree: null,
      },
    });

    expect(aggregate.identity).toEqual({
      documentId: "doc_1",
      workspaceId: "ws_1",
    });
    expect(aggregate.head).toEqual({
      title: "页面标题",
      updatedAt: "2026-04-22T12:00:00.000Z",
      permissions: {
        readOnly: true,
        disableDownload: true,
        disableCopy: false,
      },
    });
    expect(aggregate.layout.pageOptions).toMatchObject({
      wideLayout: true,
      smallText: true,
      showHeadingNumbers: false,
      showToc: true,
      collapseBacklinks: true,


permissions: {
        readOnly: true,
        disableDownload: true,
        disableCopy: false,
      },
    });
    expect(aggregate.layout.pageOptions).toMatchObject({
      wideLayout: true,
      smallText: true,
      showHeadingNumbers: false,
      showToc: true,
      collapseBacklinks: true,
      layoutDensity: "compact",
      hideChildPages: true,
      showBlockRefCount: true,
      embedDefaultBlockId: "block_1",
    });
    expect(aggregate.body).toEqual({
      content: [{ id: "block_1", type: "paragraph", content: [] }],
      revision: 8,
      conflictDetectionKey: "doc_1:8",
    });
    expect(aggregate.stats).toEqual({
      wordCount: 20,
      characterCount: 40,
      blockCount: 2,
      todoTotal: 3,
      todoDone: 1,
    });
  });
});


import { buildPageAggregate, type PageAggregateProjection } from "@/lib/documents/page-aggregate";
import { normalizeDocumentContentResponse } from "@/lib/documents/page-subtree-response";
import type { PageSubtreeProjection } from "@/lib/documents/page-subtree";
import type {
  DocumentStats,
  PageFont,
  PageLayoutDensity,
  PageOptionsState,
} from "@/types/page-options";

export type DocumentMetaPayload = {
  id: string;
  workspace_id: string;
  title: string | null;
  updated_at: string | null;
  can_edit?: boolean | null;
  disable_download?: boolean | null;
  disable_copy?: boolean | null;
  wide_layout?: boolean | null;
  use_small_text?: boolean | null;
  show_heading_numbers?: boolean | null;
  show_toc?: boolean | null;
  show_structure?: boolean | null;
  protect_editing?: boolean | null;
  show_word_count?: boolean | null;
  collapse_backlinks?: boolean | null;
  page_font?: PageFont | null;
  layout_density?: PageLayoutDensity | null;
  hide_child_pages?: boolean | null;
  show_block_ref_count?: boolean | null;
  embed_default_block_id?: string | null;
  word_count?: number | null;
  character_count?: number | null;
  block_count?: number | null;
  todo_total?: number | null;
  todo_total_count?: number | null;
  todo_done?: number | null;
  todo_done_count?: number | null;
};

export type DocumentContentPayload = {
  content?: unknown;
  revision?: number | null;
  conflict_detection_key?: string | null;
  conflictDetectionKey?: string | null;
  page_subtree?: PageSubtreeProjection | null;
  pageSubtree?: PageSubtreeProjection | null;
  title?: string | null;
};

export function buildPageAggregateFromDocumentPayloads(input: {
  meta: DocumentMetaPayload;
  contentPayload?: DocumentContentPayload | null;
}): PageAggregateProjection {
  const normalizedContent = normalizeDocumentContentResponse({
    documentId: input.meta.id,
    title: input.meta.title ?? "无标题",
    payload: input.contentPayload,
  });


};

export function buildPageAggregateFromDocumentPayloads(input: {
  meta: DocumentMetaPayload;
  contentPayload?: DocumentContentPayload | null;
}): PageAggregateProjection {
  const normalizedContent = normalizeDocumentContentResponse({
    documentId: input.meta.id,
    title: input.meta.title ?? "无标题",
    payload: input.contentPayload,
  });

  const pageOptions: PageOptionsState = {
    wideLayout: input.meta.wide_layout ?? false,
    smallText: input.meta.use_small_text ?? false,
    showHeadingNumbers: input.meta.show_heading_numbers ?? false,
    showToc: input.meta.show_toc ?? false,
    showStructure: input.meta.show_structure ?? false,
    protectEditing: input.meta.protect_editing ?? false,
    showWordCount: input.meta.show_word_count ?? true,
    collapseBacklinks: input.meta.collapse_backlinks ?? false,
    pageFont: input.meta.page_font ?? "default",
    layoutDensity: input.meta.layout_density ?? "normal",
    hideChildPages: input.meta.hide_child_pages ?? false,
    showBlockRefCount: input.meta.show_block_ref_count ?? false,
    embedDefaultBlockId: input.meta.embed_default_block_id ?? null,
  };

  const stats: DocumentStats = {
    wordCount: input.meta.word_count ?? 0,
    characterCount: input.meta.character_count ?? 0,
    blockCount: input.meta.block_count ?? 0,
    todoTotal: input.meta.todo_total ?? input.meta.todo_total_count ?? 0,
    todoDone: input.meta.todo_done ?? input.meta.todo_done_count ?? 0,
  };

  return buildPageAggregate({
    documentId: input.meta.id,
    workspaceId: input.meta.workspace_id,
    title: input.meta.title ?? "无标题",
    updatedAt: input.meta.updated_at,
    readOnly: input.meta.can_edit === false,
    disableDownload: Boolean(input.meta.disable_download),
    disableCopy: Boolean(input.meta.disable_copy),
    pageOptions,
    content: normalizedContent.content,
    revision: normalizedContent.revision,
    conflictDetectionKey: normalizedContent.conflictDetectionKey,
    pageSubtree: normalizedContent.pageSubtree,
    stats,
  });
}


import { headers } from "next/headers";
import { resolveMnoteWebInternalUrl } from "@/lib/mnote-web/internal-url";
import type { PageAggregateProjection } from "@/lib/documents/page-aggregate";

const FORWARDED_REQUEST_HEADERS = [
  "cookie",
  "authorization",
  "x-request-id",
  "x-mnote-request-id",
  "x-trace-id",
  "x-mnote-trace-id",
  "x-session-id",
  "x-mnote-session-id",
  "x-source-channel",
  "x-mnote-source-channel",
  "x-source-client",
  "x-mnote-source-client",
  "user-agent",
] as const;

const RUST_PAGE_AGGREGATE_TIMEOUT_MS = 3_000;

export type LoadedPageAggregate = {
  page: PageAggregateProjection;
  bridge: {
    requestId: string;
    traceId: string;
    queryName: "documents.page.get";
  };
};

function copyForwardHeaderIfPresent(target: Headers, source: Headers, name: string) {
  const value = source.get(name);
  if (value) {
    target.set(name, value);
  }
}

function readHeaderAlias(source: Headers, ...names: string[]) {
  for (const name of names) {
    const value = source.get(name);
    if (value) return value;
  }
  return "";
}

function copyNormalizedHeader(target: Headers, source: Headers, targetName: string, ...sourceNames: string[]) {
  const value = readHeaderAlias(source, ...sourceNames);
  if (value) {
    target.set(targetName, value);
  }
}

function buildForwardHeaders(source: Headers) {
  const requestHeaders = new Headers({
    accept: "application/json",
  });
  copyForwardHeaderIfPresent(requestHeaders, source, "cookie");
  copyForwardHeaderIfPresent(requestHeaders, source, "authorization");
  copyNormalizedHeader(requestHeaders, source, "x-request-id", "x-request-id", "x-mnote-request-id");


if (value) {
    target.set(targetName, value);
  }
}

function buildForwardHeaders(source: Headers) {
  const requestHeaders = new Headers({
    accept: "application/json",
  });
  copyForwardHeaderIfPresent(requestHeaders, source, "cookie");
  copyForwardHeaderIfPresent(requestHeaders, source, "authorization");
  copyNormalizedHeader(requestHeaders, source, "x-request-id", "x-request-id", "x-mnote-request-id");
  copyNormalizedHeader(requestHeaders, source, "x-mnote-request-id", "x-mnote-request-id", "x-request-id");
  copyNormalizedHeader(requestHeaders, source, "x-trace-id", "x-trace-id", "x-mnote-trace-id");
  copyNormalizedHeader(requestHeaders, source, "x-mnote-trace-id", "x-mnote-trace-id", "x-trace-id");
  copyNormalizedHeader(requestHeaders, source, "x-session-id", "x-session-id", "x-mnote-session-id");
  copyNormalizedHeader(requestHeaders, source, "x-mnote-session-id", "x-mnote-session-id", "x-session-id");
  copyNormalizedHeader(requestHeaders, source, "x-source-channel", "x-source-channel", "x-mnote-source-channel");
  copyNormalizedHeader(requestHeaders, source, "x-mnote-source-channel", "x-mnote-source-channel", "x-source-channel");
  copyNormalizedHeader(requestHeaders, source, "x-source-client", "x-source-client", "x-mnote-source-client");
  copyNormalizedHeader(requestHeaders, source, "x-mnote-source-client", "x-mnote-source-client", "x-source-client");
  copyForwardHeaderIfPresent(requestHeaders, source, "user-agent");
  return requestHeaders;
}

type RustPageAggregateSnapshotResponse = {
  ok?: boolean;
  schema?: string;
  result?: unknown;
  requestId?: string | null;
  traceId?: string | null;
};

function isRecord(value: unknown): value is Record<string, unknown> {
  return Boolean(value) && typeof value === "object";
}

function isBoolean(value: unknown) {
  return typeof value === "boolean";
}

function isNullableNumber(value: unknown) {
  return value === null || typeof value === "number";
}

function isNullableString(value: unknown) {
  return value === null || typeof value === "string";
}

function isTrustedMnoteWebInternalUrl(raw: string) {
  try {
    const url = new URL(raw);
    const hostname = url.hostname.replace(/^\[(.*)\]$/, "$1").trim().toLowerCase();
    return (
      hostname === "localhost" ||
      hostname === "127.0.0.1" ||
      hostname === "::1" ||
      hostname === "0.0.0.0" ||
      hostname === "::"
    );


function isTrustedMnoteWebInternalUrl(raw: string) {
  try {
    const url = new URL(raw);
    const hostname = url.hostname.replace(/^\[(.*)\]$/, "$1").trim().toLowerCase();
    return (
      hostname === "localhost" ||
      hostname === "127.0.0.1" ||
      hostname === "::1" ||
      hostname === "0.0.0.0" ||
      hostname === "::"
    );
  } catch {
    return false;
  }
}

function isRustPageAggregateProjection(
  value: unknown,
  input: { documentId: string; workspaceId?: string | null },
): value is PageAggregateProjection {
  if (!isRecord(value)) return false;
  const record = value as Record<string, unknown>;
  const identity = isRecord(record.identity) ? record.identity : null;
  const head = isRecord(record.head) ? record.head : null;
  const permissions = head && isRecord(head.permissions) ? head.permissions : null;
  const layout = isRecord(record.layout) ? record.layout : null;
  const pageOptions = layout && isRecord(layout.pageOptions) ? layout.pageOptions : null;
  const body = isRecord(record.body) ? record.body : null;
  const tree = isRecord(record.tree) ? record.tree : null;
  const stats = isRecord(record.stats) ? record.stats : null;
  const requestedWorkspaceId = input.workspaceId?.trim() || null;
  return (
    record.schema === "mnote.page_aggregate.v1" &&
    typeof record.projectionVersion === "number" &&
    typeof record.source === "string" &&
    Boolean(identity) &&
    identity.documentId === input.documentId &&
    typeof identity.workspaceId === "string" &&
    (!requestedWorkspaceId || identity.workspaceId === requestedWorkspaceId) &&
    Boolean(head) &&
    typeof head.title === "string" &&
    isNullableString(head.updatedAt) &&
    Boolean(permissions) &&
    isBoolean(permissions.readOnly) &&
    isBoolean(permissions.disableDownload) &&
    isBoolean(permissions.disableCopy) &&
    Boolean(layout) &&
    Boolean(pageOptions) &&
    Boolean(body) &&
    "content" in body &&
    isNullableNumber(body.revision) &&
    isNullableString(body.conflictDetectionKey) &&
    Boolean(tree) &&
    Object.prototype.hasOwnProperty.call(tree, "pageSubtree") &&
    Boolean(stats) &&
    typeof stats.wordCount === "number" &&
    typeof stats.characterCount === "number" &&
    typeof stats.blockCount === "number" &&
    typeof stats.todoTotal === "number" &&


Boolean(pageOptions) &&
    Boolean(body) &&
    "content" in body &&
    isNullableNumber(body.revision) &&
    isNullableString(body.conflictDetectionKey) &&
    Boolean(tree) &&
    Object.prototype.hasOwnProperty.call(tree, "pageSubtree") &&
    Boolean(stats) &&
    typeof stats.wordCount === "number" &&
    typeof stats.characterCount === "number" &&
    typeof stats.blockCount === "number" &&
    typeof stats.todoTotal === "number" &&
    typeof stats.todoDone === "number"
  );
}

export async function loadPageAggregateFromRustSnapshot(input: {
  request: Request;
  documentId: string;
  workspaceId?: string | null;
}): Promise<LoadedPageAggregate | null> {
  const internalBaseUrl = await resolveMnoteWebInternalUrl();
  if (!isTrustedMnoteWebInternalUrl(internalBaseUrl)) {
    return null;
  }
  const url = new URL(`/api/page-aggregate/${encodeURIComponent(input.documentId)}`, `${internalBaseUrl}/`);
  const workspaceId = input.workspaceId?.trim();
  if (workspaceId) {
    url.searchParams.set("workspaceId", workspaceId);
  }

  let response: Response;
  try {
    response = await fetch(url.toString(), {
      method: "GET",
      headers: buildForwardHeaders(input.request.headers),
      cache: "no-store",
      signal: AbortSignal.timeout(RUST_PAGE_AGGREGATE_TIMEOUT_MS),
    });
  } catch {
    return null;
  }

  if (!response.ok) {
    return null;
  }

  const payload = await response.json().catch(() => null) as RustPageAggregateSnapshotResponse | null;
  const page = payload?.result;
  if (
    payload?.schema !== "mnote.page_aggregate.v1" ||
    !isRustPageAggregateProjection(page, input)
  ) {
    return null;
  }

  return {
    page,
    bridge: {
      requestId: typeof payload.requestId === "string" && payload.requestId.trim() ? payload.requestId : "unknown",


const page = payload?.result;
  if (
    payload?.schema !== "mnote.page_aggregate.v1" ||
    !isRustPageAggregateProjection(page, input)
  ) {
    return null;
  }

  return {
    page,
    bridge: {
      requestId: typeof payload.requestId === "string" && payload.requestId.trim() ? payload.requestId : "unknown",
      traceId: typeof payload.traceId === "string" && payload.traceId.trim() ? payload.traceId : "unknown",
      queryName: "documents.page.get",
    },
  };
}

async function buildServerBridgeRequest(pathname: string): Promise<Request> {
  const headerList = await headers();
  const requestHeaders = new Headers();
  FORWARDED_REQUEST_HEADERS.forEach((name) => {
    copyForwardHeaderIfPresent(requestHeaders, headerList, name);
  });
  const internalBaseUrl = await resolveMnoteWebInternalUrl();

  return new Request(new URL(pathname, `${internalBaseUrl}/`).toString(), {
    method: "GET",
    headers: requestHeaders,
  });
}

export async function loadPageAggregate(input: {
  request: Request;
  documentId: string;
  workspaceId?: string | null;
}): Promise<LoadedPageAggregate | null> {
  return loadPageAggregateFromRustSnapshot(input);
}

export async function loadPageAggregateFromNextHeaders(input: {
  documentId: string;
  workspaceId?: string | null;
}): Promise<LoadedPageAggregate | null> {
  const request = await buildServerBridgeRequest("/documents/page");
  return loadPageAggregate({
    request,
    documentId: input.documentId,
    workspaceId: input.workspaceId,
  });
}


import { describe, expect, it } from "vitest";
import { buildPageAggregate } from "@/lib/documents/page-aggregate";
import type { PageOptionsState } from "@/types/page-options";

const pageOptions: PageOptionsState = {
  wideLayout: true,
  smallText: false,
  showHeadingNumbers: true,
  showToc: true,
  showStructure: false,
  protectEditing: false,
  showWordCount: true,
  collapseBacklinks: false,
  pageFont: "default",
  layoutDensity: "normal",
  hideChildPages: false,
  showBlockRefCount: false,
  embedDefaultBlockId: null,
};

describe("page-aggregate", () => {
  it("将页面 route 所需真相收口为统一聚合对象", () => {
    const aggregate = buildPageAggregate({
      documentId: "doc_1",
      workspaceId: "ws_1",
      title: " 页面标题 ",
      updatedAt: "2026-04-21T12:00:00.000Z",
      readOnly: true,
      disableDownload: true,
      disableCopy: false,
      pageOptions,
      content: [{ id: "block_1", type: "paragraph", content: [] }],
      revision: 7,
      conflictDetectionKey: "doc_1:7",
      pageSubtree: null,
      stats: {
        wordCount: 10,
        characterCount: 20,
        blockCount: 1,
        todoTotal: 0,
        todoDone: 0,
      },
    });

    expect(aggregate).toEqual({
      identity: {
        documentId: "doc_1",
        workspaceId: "ws_1",
      },
      head: {
        title: "页面标题",
        updatedAt: "2026-04-21T12:00:00.000Z",
        permissions: {
          readOnly: true,
          disableDownload: true,
          disableCopy: false,
        },
      },
      layout: {
        pageOptions,


},
      head: {
        title: "页面标题",
        updatedAt: "2026-04-21T12:00:00.000Z",
        permissions: {
          readOnly: true,
          disableDownload: true,
          disableCopy: false,
        },
      },
      layout: {
        pageOptions,
      },
      body: {
        content: [{ id: "block_1", type: "paragraph", content: [] }],
        revision: 7,
        conflictDetectionKey: "doc_1:7",
      },
      tree: {
        pageSubtree: null,
      },
      stats: {
        wordCount: 10,
        characterCount: 20,
        blockCount: 1,
        todoTotal: 0,
        todoDone: 0,
      },
    });
  });

  it("缺省标题与可选字段时回退到稳定默认值", () => {
    const aggregate = buildPageAggregate({
      documentId: "doc_2",
      workspaceId: "ws_2",
      pageOptions,
      content: null,
    });

    expect(aggregate.head.title).toBe("无标题");
    expect(aggregate.head.permissions).toEqual({
      readOnly: false,
      disableDownload: false,
      disableCopy: false,
    });
    expect(aggregate.body).toEqual({
      content: null,
      revision: null,
      conflictDetectionKey: null,
    });
    expect(aggregate.tree.pageSubtree).toBeNull();
    expect(aggregate.stats).toBeNull();
  });
});


import type { PageSubtreeProjection } from "@/lib/documents/page-subtree";
import type { DocumentStats, PageOptionsState } from "@/types/page-options";

export interface PageAggregateIdentity {
  documentId: string;
  workspaceId: string;
}

export interface PageAggregatePermissions {
  readOnly: boolean;
  disableDownload: boolean;
  disableCopy: boolean;
}

export interface PageAggregateHead {
  title: string;
  updatedAt: string | null;
  permissions: PageAggregatePermissions;
}

export interface PageAggregateLayout {
  pageOptions: PageOptionsState;
}

export interface PageAggregateBody {
  content: unknown;
  revision: number | null;
  conflictDetectionKey: string | null;
}

export interface PageAggregateTree {
  pageSubtree: PageSubtreeProjection | null;
}

export interface PageAggregateProjection {
  schema?: "mnote.page_aggregate.v1";
  projectionVersion?: number;
  source?: "KernelProjection" | "CompatMetaContentJoin" | "Fixture" | string;
  identity: PageAggregateIdentity;
  head: PageAggregateHead;
  layout: PageAggregateLayout;
  body: PageAggregateBody;
  tree: PageAggregateTree;
  stats: DocumentStats | null;
}

export function buildPageAggregate(input: {
  documentId: string;
  workspaceId: string;
  title?: string | null;
  updatedAt?: string | null;
  readOnly?: boolean;
  disableDownload?: boolean;
  disableCopy?: boolean;
  pageOptions: PageOptionsState;
  content: unknown;
  revision?: number | null;
  conflictDetectionKey?: string | null;
  pageSubtree?: PageSubtreeProjection | null;
  stats?: DocumentStats | null;


workspaceId: string;
  title?: string | null;
  updatedAt?: string | null;
  readOnly?: boolean;
  disableDownload?: boolean;
  disableCopy?: boolean;
  pageOptions: PageOptionsState;
  content: unknown;
  revision?: number | null;
  conflictDetectionKey?: string | null;
  pageSubtree?: PageSubtreeProjection | null;
  stats?: DocumentStats | null;
}): PageAggregateProjection {
  return {
    identity: {
      documentId: input.documentId,
      workspaceId: input.workspaceId,
    },
    head: {
      title: input.title?.trim() || "无标题",
      updatedAt: input.updatedAt ?? null,
      permissions: {
        readOnly: Boolean(input.readOnly),
        disableDownload: Boolean(input.disableDownload),
        disableCopy: Boolean(input.disableCopy),
      },
    },
    layout: {
      pageOptions: input.pageOptions,
    },
    body: {
      content: input.content ?? null,
      revision: input.revision ?? null,
      conflictDetectionKey: input.conflictDetectionKey ?? null,
    },
    tree: {
      pageSubtree: input.pageSubtree ?? null,
    },
    stats: input.stats ?? null,
  };
}