5a6009c750
Узел drawio для агента был непрозрачен: round-trip хранит узел, но содержимое
диаграммы недоступно. Стадия 1 даёт минимальный CRUD без рендеринга на беке.
Новые модули:
- drawio-xml.ts: decode-chain (content= base64 -> plain/entity-encoded XML или
compressed <diagram> через pako.inflateRaw raw-deflate + decodeURIComponent;
инфляция потоковая с капом 16 MiB — защита от decompression-bomb); encode
(plain uncompressed по контракту createDrawioSvg); линтер (все правила ->
структурированный tool-error с cellId; edge без дочернего mxGeometry — ошибка
№1); stable mxHash (sha256 по нормализованному XML) — ключ optimistic-lock.
- drawio-preview.ts: чистый TS schematic SVG (rect/ellipse/rhombus/edge/label,
контейнеры -> абсолютные координаты, unknown-стенсил -> подписанный rect),
без зависимостей и без бэкенд-рендера.
Инструменты (SHARED_TOOL_SPECS, deferred-тир):
- drawio_get(pageId, node, format?) -> XML/SVG + мета {attachmentId,title,w,h,
cellCount,hash}; читает человеческий compressed-экспорт losslessly.
- drawio_create(pageId, where, xml, title?) -> lint -> preview -> .drawio.svg ->
upload (тот же attachment-конвейер + validateDocUrls) -> insert drawio-узел;
возвращает адресуемый '#<index>'-хендл (у схемы drawio нет атрибута id).
- drawio_update(pageId, node, xml, baseHash) -> baseHash-конфликт до записи;
перепривязывает РОВНО адресованный узел (не все с общим attachmentId).
pako@2.0.3 — единственная новая зависимость (lockfile синхронизирован).
closes #423
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
62 lines
1.9 KiB
TypeScript
62 lines
1.9 KiB
TypeScript
// Minimal ambient type declaration for `pako` (no @types/pako is installed and
|
|
// pako 2.x ships no bundled .d.ts). We only use the raw-deflate codec to read
|
|
// draw.io's compressed `<diagram>` payload, so declare just that surface.
|
|
declare module "pako" {
|
|
interface RawOptions {
|
|
/** When "string", the result is returned as a (binary/UTF-8) string. */
|
|
to?: "string";
|
|
/** Raw-deflate window bits; draw.io uses raw deflate (no zlib header). */
|
|
windowBits?: number;
|
|
level?: number;
|
|
}
|
|
|
|
/** Raw-inflate (windowBits: -15). `to:"string"` yields a string. */
|
|
export function inflateRaw(
|
|
data: Uint8Array | ArrayBuffer | number[],
|
|
options: RawOptions & { to: "string" },
|
|
): string;
|
|
export function inflateRaw(
|
|
data: Uint8Array | ArrayBuffer | number[],
|
|
options?: RawOptions,
|
|
): Uint8Array;
|
|
|
|
/** Raw-deflate (windowBits: -15). Used only by tests to build fixtures. */
|
|
export function deflateRaw(
|
|
data: Uint8Array | string,
|
|
options?: RawOptions,
|
|
): Uint8Array;
|
|
|
|
interface InflateStreamOptions {
|
|
to?: "string";
|
|
windowBits?: number;
|
|
/** Raw deflate (no zlib header) — equivalent to windowBits: -15. */
|
|
raw?: boolean;
|
|
chunkSize?: number;
|
|
}
|
|
|
|
/**
|
|
* Streaming inflate. We use it to bound the decompressed size: `onData` is
|
|
* invoked per output chunk, letting us abort a decompression bomb before the
|
|
* full output is materialised.
|
|
*/
|
|
export class Inflate {
|
|
constructor(options?: InflateStreamOptions);
|
|
onData: (chunk: string | Uint8Array) => void;
|
|
onEnd: (status: number) => void;
|
|
push(
|
|
data: Uint8Array | ArrayBuffer | number[] | string,
|
|
flushMode?: boolean | number,
|
|
): boolean;
|
|
result: string | Uint8Array;
|
|
err: number;
|
|
msg: string;
|
|
}
|
|
|
|
const _default: {
|
|
inflateRaw: typeof inflateRaw;
|
|
deflateRaw: typeof deflateRaw;
|
|
Inflate: typeof Inflate;
|
|
};
|
|
export default _default;
|
|
}
|