@f-o-t/markdown 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,305 @@
1
+ # @f-o-t/markdown
2
+
3
+ A CommonMark compliant markdown parser with AST generation, streaming support, and bidirectional conversion (Markdown ↔ HTML).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # npm
9
+ npm install @f-o-t/markdown
10
+
11
+ # pnpm
12
+ pnpm add @f-o-t/markdown
13
+
14
+ # bun
15
+ bun add @f-o-t/markdown
16
+ ```
17
+
18
+ ## Features
19
+
20
+ - **CommonMark compliant** - Full support for the CommonMark specification
21
+ - **GFM extensions** - Tables, task lists, strikethrough
22
+ - **Bidirectional conversion** - Parse markdown to AST, generate markdown from AST
23
+ - **HTML rendering** - Convert markdown AST to HTML with customization options
24
+ - **HTML to Markdown** - Convert HTML content back to markdown
25
+ - **Streaming support** - Process large files efficiently with streaming APIs
26
+ - **Batch processing** - Parse multiple files with progress events
27
+ - **Type-safe** - Full TypeScript support with Zod schemas for runtime validation
28
+ - **Zero dependencies** (except Zod for schema validation)
29
+
30
+ ## Quick Start
31
+
32
+ ### Parsing Markdown
33
+
34
+ ```typescript
35
+ import { parse, parseOrThrow } from "@f-o-t/markdown";
36
+
37
+ // Safe parsing with result type
38
+ const result = parse("# Hello **World**");
39
+ if (result.success) {
40
+ console.log(result.data.root.children);
41
+ }
42
+
43
+ // Throws on error
44
+ const doc = parseOrThrow("# Hello **World**");
45
+ console.log(doc.root.children);
46
+ ```
47
+
48
+ ### Generating Markdown
49
+
50
+ ```typescript
51
+ import { generate, generateHeadingString, generateLinkString } from "@f-o-t/markdown";
52
+
53
+ // Generate from AST
54
+ const markdown = generate(doc);
55
+
56
+ // Helper functions for common elements
57
+ generateHeadingString(1, "Title"); // "# Title"
58
+ generateLinkString("Example", "https://example.com"); // "[Example](https://example.com)"
59
+ ```
60
+
61
+ ### HTML Rendering
62
+
63
+ ```typescript
64
+ import { parse, renderToHtml } from "@f-o-t/markdown";
65
+
66
+ const doc = parse("# Hello **World**");
67
+ const html = renderToHtml(doc.data);
68
+ // => "<h1 id="hello-world">Hello <strong>World</strong></h1>"
69
+ ```
70
+
71
+ ### HTML to Markdown
72
+
73
+ ```typescript
74
+ import { htmlToMarkdown } from "@f-o-t/markdown";
75
+
76
+ const markdown = htmlToMarkdown("<h1>Hello</h1><p>World</p>");
77
+ // => "# Hello\n\nWorld"
78
+ ```
79
+
80
+ ## API Reference
81
+
82
+ ### Parsing
83
+
84
+ | Function | Description |
85
+ |----------|-------------|
86
+ | `parse(content, options?)` | Parses markdown, returns `{ success, data }` or `{ success, error }` |
87
+ | `parseOrThrow(content, options?)` | Parses markdown, throws on error |
88
+ | `parseToAst(content, options?)` | Returns just the AST root node |
89
+ | `parseBuffer(buffer, options?)` | Parses from `Uint8Array` with encoding detection |
90
+ | `parseBufferOrThrow(buffer, options?)` | Same as above, throws on error |
91
+ | `isValidMarkdown(content)` | Returns `true` if content can be parsed |
92
+
93
+ #### Parse Options
94
+
95
+ ```typescript
96
+ interface ParseOptions {
97
+ positions?: boolean; // Include position info (default: true)
98
+ preserveSource?: boolean; // Keep original source (default: false)
99
+ }
100
+ ```
101
+
102
+ ### Utility Functions
103
+
104
+ ```typescript
105
+ import {
106
+ extractText,
107
+ countWords,
108
+ getHeadings,
109
+ getLinks,
110
+ getImages,
111
+ getCodeBlocks,
112
+ } from "@f-o-t/markdown";
113
+
114
+ extractText("**Hello** *world*"); // "Hello world"
115
+ countWords("Hello **world**!"); // 2
116
+ getHeadings("# Title\n## Section"); // [{ level: 1, text: "Title" }, ...]
117
+ getLinks("[Example](url)"); // [{ text: "Example", url: "url" }]
118
+ getImages("![Alt](image.png)"); // [{ alt: "Alt", url: "image.png" }]
119
+ getCodeBlocks("```js\ncode\n```"); // [{ lang: "js", code: "code" }]
120
+ ```
121
+
122
+ ### Generation
123
+
124
+ | Function | Description |
125
+ |----------|-------------|
126
+ | `generate(document, options?)` | Generates markdown from AST |
127
+ | `generateNode(node, options?)` | Generates markdown from any node |
128
+ | `createGenerator(options?)` | Creates incremental generator |
129
+
130
+ #### Generate Options
131
+
132
+ ```typescript
133
+ interface GenerateOptions {
134
+ lineEnding?: "\n" | "\r\n"; // Default: "\n"
135
+ indent?: number; // List indent (default: 3)
136
+ setext?: boolean; // Setext headings (default: false)
137
+ fence?: "`" | "~"; // Code fence char (default: "`")
138
+ fenceLength?: number; // Fence length (default: 3)
139
+ emphasis?: "*" | "_"; // Emphasis marker (default: "*")
140
+ strong?: "**" | "__"; // Strong marker (default: "**")
141
+ bullet?: "-" | "*" | "+"; // List bullet (default: "-")
142
+ }
143
+ ```
144
+
145
+ #### String Helpers
146
+
147
+ ```typescript
148
+ import {
149
+ generateHeadingString,
150
+ generateLinkString,
151
+ generateImageString,
152
+ generateCodeBlockString,
153
+ generateListString,
154
+ generateBlockquoteString,
155
+ generateEmphasisString,
156
+ generateStrongString,
157
+ generateInlineCodeString,
158
+ generateTableString,
159
+ generateTaskListString,
160
+ generateStrikethroughString,
161
+ } from "@f-o-t/markdown";
162
+
163
+ // Examples
164
+ generateHeadingString(2, "Section"); // "## Section"
165
+ generateCodeBlockString("const x = 1;", "js"); // "```js\nconst x = 1;\n```"
166
+ generateListString(["A", "B"], true); // "1. A\n2. B"
167
+ generateTableString(["Name", "Age"], [["Alice", "30"]]);
168
+ generateTaskListString([{ text: "Done", checked: true }]);
169
+ ```
170
+
171
+ ### HTML Rendering
172
+
173
+ ```typescript
174
+ import { renderToHtml, renderNodeToHtml } from "@f-o-t/markdown";
175
+
176
+ const html = renderToHtml(document, {
177
+ sanitizeHtml: true, // Escape raw HTML (default: true)
178
+ externalLinksNewTab: false, // Add target="_blank" (default: false)
179
+ classPrefix: "", // CSS class prefix
180
+ softBreakAsBr: false, // Render soft breaks as <br>
181
+ transformUrl: (url, type) => url, // Custom URL transformer
182
+ elementAttributes: { // Custom attributes per element
183
+ link: { rel: "nofollow" },
184
+ image: { loading: "lazy" },
185
+ },
186
+ });
187
+ ```
188
+
189
+ ### HTML to Markdown
190
+
191
+ ```typescript
192
+ import { htmlToMarkdown, parseHtml, htmlAstToMarkdownAst } from "@f-o-t/markdown";
193
+
194
+ // Direct conversion
195
+ const markdown = htmlToMarkdown("<h1>Hello</h1>");
196
+
197
+ // Or step by step
198
+ const htmlAst = parseHtml("<h1>Hello</h1>");
199
+ const markdownAst = htmlAstToMarkdownAst(htmlAst);
200
+ ```
201
+
202
+ ### Streaming
203
+
204
+ ```typescript
205
+ import {
206
+ parseStream,
207
+ parseStreamToDocument,
208
+ parseBatchStream,
209
+ } from "@f-o-t/markdown";
210
+
211
+ // Stream from fetch
212
+ const response = await fetch("large-doc.md");
213
+ for await (const event of parseStream(response.body)) {
214
+ if (event.type === "block") {
215
+ console.log("Parsed block:", event.data.type);
216
+ } else if (event.type === "complete") {
217
+ console.log("Done!");
218
+ }
219
+ }
220
+
221
+ // Collect to document
222
+ const doc = await parseStreamToDocument(response.body);
223
+
224
+ // Batch processing
225
+ const files = [
226
+ { filename: "a.md", content: "# A" },
227
+ { filename: "b.md", content: "# B" },
228
+ ];
229
+ for await (const event of parseBatchStream(files)) {
230
+ switch (event.type) {
231
+ case "file_start":
232
+ console.log(`Processing ${event.filename}`);
233
+ break;
234
+ case "file_complete":
235
+ console.log(`Completed ${event.filename}`);
236
+ break;
237
+ case "batch_complete":
238
+ console.log(`Done: ${event.totalFiles} files`);
239
+ break;
240
+ }
241
+ }
242
+ ```
243
+
244
+ #### Stream Options
245
+
246
+ ```typescript
247
+ interface StreamOptions {
248
+ positions?: boolean; // Include positions (default: true)
249
+ maxBufferSize?: number; // Max buffer size in bytes (default: 10MB)
250
+ chunkSize?: number; // Chunk size for batch (default: 64KB)
251
+ }
252
+ ```
253
+
254
+ ### AST Types
255
+
256
+ All node types are available as TypeScript types and Zod schemas:
257
+
258
+ ```typescript
259
+ import type {
260
+ MarkdownDocument,
261
+ DocumentNode,
262
+ BlockNode,
263
+ InlineNode,
264
+ HeadingNode,
265
+ ParagraphNode,
266
+ CodeBlockNode,
267
+ ListNode,
268
+ TableNode,
269
+ // ... and more
270
+ } from "@f-o-t/markdown";
271
+
272
+ import {
273
+ documentNodeSchema,
274
+ headingNodeSchema,
275
+ paragraphNodeSchema,
276
+ // ... Zod schemas for validation
277
+ } from "@f-o-t/markdown";
278
+ ```
279
+
280
+ ### Utilities
281
+
282
+ ```typescript
283
+ import {
284
+ normalizeLineEndings,
285
+ normalizeEscapedNewlines,
286
+ normalizeMarkdownEmphasis,
287
+ } from "@f-o-t/markdown";
288
+
289
+ // Normalize to \n
290
+ normalizeLineEndings("hello\r\nworld"); // "hello\nworld"
291
+
292
+ // Convert escaped newlines to actual newlines
293
+ normalizeEscapedNewlines("hello\\nworld"); // "hello\nworld"
294
+ ```
295
+
296
+ ## CommonMark Compliance
297
+
298
+ This library implements the [CommonMark specification](https://commonmark.org/) with additional support for GFM (GitHub Flavored Markdown) extensions:
299
+
300
+ - **Block elements**: Headings, paragraphs, code blocks (fenced and indented), blockquotes, lists (ordered, unordered, task lists), thematic breaks, HTML blocks, tables
301
+ - **Inline elements**: Emphasis, strong emphasis, links, images, code spans, hard/soft breaks, HTML inline, strikethrough
302
+
303
+ ## License
304
+
305
+ MIT