# npm — `@minjun0219/mdwire`

> @minjun0219/mdwire 패키지 — render, renderWithReport, Streamer, 그리고 React · events 진입점.

HTML: https://mdwire.minjun.dev/ko/docs/npm/

Rust 코어를 WASM 으로 컴파일하고, React 와 이벤트 목록용 JS 를 손으로 써서 더했다. Node · Bun ·
번들러에서 돈다.

```sh
npm install @minjun0219/mdwire
```

패키지는 `exports` 조건으로 빌드를 고른다 — Node 는 WASM 을 디스크에서 읽는 CommonJS 빌드, 나머지는
ESM 번들러 빌드. **번들러에서는** WASM 을 ES 모듈로 import 하므로 Vite 는 `vite-plugin-wasm` 과
`build.target: "esnext"`(top-level `await`)가 필요하다.
[`examples/react-streaming/vite.config.js`](https://github.com/minjun0219/mdwire/blob/main/examples/react-streaming/vite.config.js) 참고.

채널과 입력 표기의 이름은 [개념](https://mdwire.minjun.dev/ko/docs/)에 있는 문자열을 쓴다.

## `render(input, channel, options?)`

```ts
import { render } from "@minjun0219/mdwire";

const parts: string[] = render(markdown, "telegram-html");
```

완성된 문서를 옮긴다. 보낼 조각들을 돌려준다 — 채널의 한도(또는 `options.limit`)를 넘지 않으면 하나뿐이다.

## `renderWithReport(input, channel, options?)`

```ts
import { renderWithReport } from "@minjun0219/mdwire";

const out = renderWithReport(markdown, "slack-markdown", { from: "slack-mrkdwn" });
out.parts;                   // string[]
out.repairs.closedEmphasis;  // 아래 "Repairs"
out.free();                  // 결과는 WASM 메모리에 있다
```

`render` 에 [정규화 보고](https://mdwire.minjun.dev/ko/docs/#%EC%A0%95%EA%B7%9C%ED%99%94-%EB%B3%B4%EA%B3%A0)를 더한 것. 결과와 그 `repairs` 는 WASM 객체라 다 쓰면
`free()` 를 부르거나 `using`(`Symbol.dispose`)으로 쓴다.

## `limit(channel)`

채널의 조각 한도(숫자). `html` 은 한도가 없어 `4294967295` 를 돌려준다.

## `new Streamer(channel, options?)`

```ts
import { Streamer } from "@minjun0219/mdwire";

const s = new Streamer("telegram-html");
let acc = "";
for await (const token of tokens) {
  acc += s.push(token);           // 확정분 — 다시 고쳐지지 않는다
  await edit(acc + s.preview());  // + 붙든 것을 입력이 여기서 끝난 것처럼 그린 꼬리
}
acc += s.finish();
if (s.revised()) await edit(acc); // 마지막 화면이 곧 완성본이면 건너뛴다
s.free();
```

| 메서드 | 돌려주는 것 | |
|---|---|---|
| `push(chunk)` | `string` | 지금 보내도 안전한 것. 확정분. |
| `preview()` | `string` | 메시지를 통째로 다시 그릴 때 붙일 꼬리. 상태를 바꾸지 않는다. 조각마다가 아니라 그릴 때 부른다. |
| `closeOpen()` | `string` | 이미 나간 블록 마크업만 닫는 꼬리. 상태를 바꾸지 않는다. |
| `finish()` | `string` | 남은 것, 열린 마크업은 닫아서. |
| `revised()` | `boolean` | `finish` 뒤에: 완성본이 마지막 `preview()` 화면과 다른가. |
| `repairs()` | `Repairs` | 지금까지 고친 것 — `finish` 뒤엔 문서 전체. |
| `free()` | | WASM 메모리를 놓는다. |

덧붙이기만 하는 채널(슬랙 `appendStream`)은 `push` 조각을 그대로 보내고 `preview()` 는 부르지 않는다.
[스트리밍 계약](https://mdwire.minjun.dev/ko/docs/#%EC%8A%A4%ED%8A%B8%EB%A6%AC%EB%B0%8D-%EA%B3%84%EC%95%BD) 참고.

## `RenderOptions`

```ts
interface RenderOptions {
  from?: "markdown" | "slack-mrkdwn";
  limit?: number;                       // 1 이상의 정수. 256 보다 작으면 256 으로 올린다
  html?: {                              // "html" 채널에만
    lineBreaks?: "br" | "space";        // 기본 "br"
    images?: "link" | "load";           // 기본 "link"
    schemes?: string[];                 // 기본 ["http", "https", "mailto"]. 바꾸는 것이지 더하는 것이 아니다
  };
}
```

모르는 채널·입력 표기 이름, 모르는 `html` 값, 1 이상의 정수가 아닌 `limit` 은 `Error` 를 던진다.

## `Repairs`

| 필드 | |
|---|---|
| `closedEmphasis` · `closedFence` · `revertedCodeSpan` · `droppedMarker` | 고친 것 |
| `escapedChar` · `tagEmphasis` · `strippedHtml` · `rewrittenBullet` · `rewrittenTable` · `convertedMarker` | 채널에 맞춰 바꾼 것 |

각각이 세는 것: [개념](https://mdwire.minjun.dev/ko/docs/#%EC%A0%95%EA%B7%9C%ED%99%94-%EB%B3%B4%EA%B3%A0).

## React — `@minjun0219/mdwire/react`

React 요소를 `createElement` 로 만든다 — `innerHTML` 을 쓰지 않는다. 이스케이프, 태그 집합, 링크 스킴은 코어의
`html` 채널이 정하고, 태그마다 어떤 컴포넌트로 그릴지만 고른다.

```tsx
import { Markdown, useMarkdownStream } from "@minjun0219/mdwire/react";

<Markdown text={answer} components={{ a: RouterLink }} />

const { elements, push, finish } = useMarkdownStream();      // push(token) … finish()
const settled = useMarkdownStream({ eager: false });        // 확정된 것만
```

| | |
|---|---|
| `<Markdown text from? components? options? />` | 완성된 답. `components` 는 태그(`a`, `code` …)를 내 컴포넌트에 잇는다. |
| `useMarkdownStream({ from?, components?, options?, eager?, onSettled? })` | 스트리밍. `{ elements, push, finish }` 를 돌려준다. `eager`(기본 `true`)는 붙든 것도 먼저 그리고, `false` 는 확정된 것만. `onSettled(html, revised)` 는 `finish` 뒤에 불린다. 옵션은 `onSettled` 말고는 처음 한 번만 읽는다. |
| `toElements(html, components?, schemes?)` | `html` 채널 출력(스트리밍이면 누적본 + `preview()`)을 React 노드로. `schemes` 는 코어에 준 것과 같게 준다. |

## 이벤트 — `@minjun0219/mdwire/events`

```ts
import { toEvents } from "@minjun0219/mdwire/events";

toEvents(html); // [{ type: "open", tag: "p", attrs: {} }, { type: "text", text: "…" }, { type: "close", tag: "p" }, …]
```

같은 `html` 출력을 `open` / `text` / `close` / `void` 의 평평한 목록으로 푼다 — React 가 아닌 프레임워크에서 쓴다. 속성은 코어가
내는 것만 담긴다: `a` 의 `href`, `code` 의 `class`(`language-…`), `th` · `td` 의 `style`(`text-align:…`),
`ol` 의 `start`, 이미지를 불러올 때 `img` 의 `src` · `alt`.
