Files
gitmost/packages/mcp/src/client.ts
T
agent_coder 14d7b21df0 refactor(mcp): распил client.ts (5206 строк) на доменные модули за тонким фасадом (#450)
client.ts был god-object'ом на 5206 строк / ~65 методов / 5 ответственностей —
любая правка рисковала всем write-path. Разнесли на доменные модули;
DocmostClient остаётся ТОНКИМ ФАСАДОМ с прежним внешним контрактом. Чистый
рефакторинг, поведение не меняется. closes #450

- Фасад client.ts (93 строки) композирует 10 миксинов над общим абстрактным
  базовым классом. Паттерн МИКСИНЫ (не context-object): тесты субклассируют
  DocmostClient и переопределяют seam'ы; единая цепочка прототипов сохраняет
  виртуальную диспетчеризацию this.<method> сквозь модули.
- client/context.ts (база): общее состояние (axios, apiUrl, токены, кэши),
  конструктор + оба интерсептора, login/ensureAuthenticated/paginateAll/
  resolvePageId/mutateLiveContentUnlocked + write-seam'ы mutatePage/replacePage.
  private→protected (внутреннее, не в публичном контракте).
- Модули (каждый ≤730): read, pages, nodes-write, media (images/attachments/
  drawio), comments, transforms, tables, stash, doc-validate. errors.ts —
  единый REST error-mapping (довершение #437; тексты сообщений без изменений).
- Внешний контракт СОХРАНЁН (доказано компиляцией с обеих сторон): 53 публичных
  метода через `implements IXMixin` (без ручного зеркала, #446); оба Pick
  (in-app loader + tool-specs) резолвятся; множество async-методов 59==59.
  Нагруженные seam'ы (replaceImage один-лок #425, self-resolve #449, единый
  error-путь) байт-идентичны; ни одного дубля публичного метода.
- zod v3→v4: investigate-only, отложено — SDK 1.29 поддерживает v4, но мажор-
  бамп трогает всю схема-поверхность; отдельным follow-up.

Стоит на #449 (#475). Тесты: mcp node --test 800/800 (== база), tsc чисто в
client/. Ни одной строки кода не потеряно; убраны 52 дублированных JSDoc-блока.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 01:04:42 +03:00

94 lines
4.9 KiB
TypeScript

// DocmostClient — thin facade assembled from domain mixins (issue #450).
//
// The 5206-line god-object was split into cohesive domain modules under
// `client/`, each a mixin layered on the shared `DocmostClientContext` base
// (which owns the axios client, auth, apiUrl, the resolvePageId cache and the
// core HTTP/write seams). The mixins compose into ONE prototype chain, so:
// - every public method name/signature is UNCHANGED (the external contract —
// `DocmostClientLike` = `Pick<DocmostClient, ...>`, the in-app adapter, and
// both transports — resolves exactly as before);
// - `this.<method>` dispatches virtually across modules, so a test subclass
// that overrides a seam (`mutatePage`, `resolvePageId`, `getPageRaw`,
// `getCollabTokenWithReauth`, `uploadAttachmentBuffer`, `fetchAttachmentText`)
// is still seen by every caller — the load-bearing #449/#425 patterns
// (single held page-lock, self-resolving write seams, the no-await critical
// window in collab-session) are preserved byte-for-byte in their modules.
//
// This file only wires the chain and re-exports the package's public surface.
import { DocmostClientContext } from "./client/context.js";
import { ReadMixin, type IReadMixin } from "./client/read.js";
import { PagesMixin, type IPagesMixin } from "./client/pages.js";
import { NodesWriteMixin, type INodesWriteMixin } from "./client/nodes-write.js";
import { TablesMixin, type ITablesMixin } from "./client/tables.js";
import { DocValidateMixin } from "./client/doc-validate.js";
import { MediaMixin, type IMediaMixin } from "./client/media.js";
import { StashMixin, type IStashMixin } from "./client/stash.js";
import { DrawioMixin, type IDrawioMixin } from "./client/drawio.js";
import { CommentsMixin, type ICommentsMixin } from "./client/comments.js";
import { TransformsMixin, type ITransformsMixin } from "./client/transforms.js";
// Re-export the package's public types + helpers from their new homes so every
// existing importer (index.ts, http.ts, stdio.ts, the in-app host) keeps working
// with ZERO changes.
export type { DocmostMcpConfig, SandboxPut } from "./client/context.js";
export { formatDocmostAxiosError, assertFullUuid } from "./client/errors.js";
// The full public + shared instance surface of the assembled client. Built by
// INTERSECTING each domain mixin's public interface (each DERIVED from its class
// and enforced by that class's `implements` clause — issue #446, no hand-mirror)
// with the shared context. Named explicitly so the emitted `.d.ts` refers to
// this type rather than the anonymous mixin-composed class (which carries the
// base's `protected` shared state and would otherwise trip TS4094). DocValidate
// exposes no PUBLIC methods (all its helpers are protected), so it contributes
// nothing to the public surface and needs no interface here.
//
// Signature fidelity: every method's NAME and PARAMETER types are preserved
// exactly (the contract surface both hosts type-check against — `DocmostClientLike
// = Pick<DocmostClient, ...>`, the in-app adapter). Explicit source return types
// (e.g. `stashPage`, `exportPageMarkdown`, the drawio tools) are carried through
// verbatim; methods that relied on inference in the monolith surface as `any`
// return here — a deliberate, contract-safe trade-off, since the execute
// wrappers and the adapter consume the arguments, not the awaited return shape.
type DocmostClientInstance = DocmostClientContext &
IReadMixin &
IPagesMixin &
INodesWriteMixin &
ITablesMixin &
IMediaMixin &
IStashMixin &
IDrawioMixin &
ICommentsMixin &
ITransformsMixin;
// Compose the domain mixins over the shared context. Order is irrelevant to
// behaviour (cross-module calls go through `this`, resolved at runtime on the
// assembled instance); it only affects the prototype-chain nesting. The
// explicit constructor-type annotation keeps the exported `.d.ts` referring to
// the NAMED `DocmostClientInstance` rather than the anonymous composed class.
const DocmostClientBase: new (...args: any[]) => DocmostClientInstance =
TransformsMixin(
CommentsMixin(
DrawioMixin(
StashMixin(
MediaMixin(
DocValidateMixin(
TablesMixin(
NodesWriteMixin(PagesMixin(ReadMixin(DocmostClientContext))),
),
),
),
),
),
),
) as unknown as new (...args: any[]) => DocmostClientInstance;
/**
* The Docmost API client used by both the standalone MCP server and the in-app
* AI-SDK host. A thin, concrete assembly of the domain mixins above; carries no
* logic of its own. See `client/` for the per-domain implementations. Its public
* method surface is UNCHANGED from the original monolith, so the external
* contract (`DocmostClientLike = Pick<DocmostClient, ...>`, the in-app adapter,
* both transports' execute-wrappers) resolves identically.
*/
export class DocmostClient extends DocmostClientBase {}