@orkestrel/markdown 0.0.6 → 0.0.7
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 +49 -64
- package/dist/src/core/index.cjs +2722 -1734
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +489 -126
- package/dist/src/core/index.d.ts +489 -126
- package/dist/src/core/index.js +2706 -1730
- package/dist/src/core/index.js.map +1 -1
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
# @orkestrel/markdown
|
|
2
2
|
|
|
3
|
-
A
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
A types-first markdown layer over `@orkestrel/html`: parse GitHub-Flavored Markdown into a typed
|
|
4
|
+
AST, project that AST out to sanitized HTML or to canonical markdown source, and project an HTML AST
|
|
5
|
+
back in.
|
|
6
|
+
|
|
7
|
+
- **One AST, several projections.** Nodes are plain readonly data keyed by `element`; parsing,
|
|
8
|
+
querying, rewriting, folding, streaming, and every conversion are operations over it.
|
|
9
|
+
- **Both directions live here.** `markdownToHTML` and `htmlToMarkdown` are inverse projections
|
|
10
|
+
across the boundary, because what an HTML subtree means in markdown is markdown's knowledge, not
|
|
11
|
+
html's.
|
|
12
|
+
- **Sanitized by default, with no opt-out.** `renderHTML` takes one argument and composes
|
|
13
|
+
`@orkestrel/html`'s sanitize floor, so there is no call shape that emits unsafe HTML.
|
|
14
|
+
- **Bounded by design.** Every parse, traversal, and projection is iterative and depth-capped, so
|
|
15
|
+
malformed or hostile input degrades to literal text instead of throwing.
|
|
9
16
|
|
|
10
17
|
## Install
|
|
11
18
|
|
|
@@ -15,88 +22,66 @@ npm install @orkestrel/markdown
|
|
|
15
22
|
|
|
16
23
|
## Requirements
|
|
17
24
|
|
|
18
|
-
- Node.js >=
|
|
19
|
-
-
|
|
25
|
+
- Node.js >= 22.12
|
|
26
|
+
- Ships ES and CommonJS builds with its own `.d.ts` types
|
|
27
|
+
- Two runtime dependencies, `@orkestrel/html` and `@orkestrel/contract`
|
|
20
28
|
|
|
21
29
|
## Usage
|
|
22
30
|
|
|
23
31
|
```ts
|
|
24
|
-
import { createMarkdown, renderHTML } from '@orkestrel/markdown'
|
|
32
|
+
import { createMarkdown, htmlToMarkdown, renderHTML, renderMarkdown } from '@orkestrel/markdown'
|
|
33
|
+
import { parseDocument as parseHTML } from '@orkestrel/html'
|
|
25
34
|
|
|
26
35
|
const markdown = createMarkdown('# Hi\n\nRead the [guide](./guide.md) for more, *thanks*.')
|
|
36
|
+
|
|
27
37
|
markdown.document
|
|
28
38
|
// { element: 'document', children: [...] } — the typed, render-agnostic AST
|
|
29
39
|
|
|
30
40
|
renderHTML(markdown.document)
|
|
31
|
-
// '<h1>Hi</h1
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
`createMarkdown(markdown)` (or `new Markdown(markdown)`) runs a two-phase
|
|
35
|
-
parse (block phase, then inline phase) and stores the result as a stateful
|
|
36
|
-
workspace's `document` — a render-agnostic `MarkdownDocument`. The workspace
|
|
37
|
-
also exposes `find` / `filter` / `map` / `reduce` / `fold` / `stream` /
|
|
38
|
-
iteration over the AST. `renderHTML(node)` HTML-escapes all text and
|
|
39
|
-
attributes and sanitizes link `href`s (an unsafe scheme like `javascript:` or
|
|
40
|
-
`data:` is dropped), so even hostile content cannot inject markup or script.
|
|
41
|
-
`renderMarkdown(node)` writes canonical markdown source back out — a
|
|
42
|
-
`parseDocument(renderMarkdown(doc))` round-trip always deep-equals `doc`. A
|
|
43
|
-
fold projects the AST to any shape (a plain string, a DOM tree, a count)
|
|
44
|
-
through one total, per-element handler table, with no writer coupling built
|
|
45
|
-
in.
|
|
46
|
-
|
|
47
|
-
## Validating untrusted ASTs
|
|
48
|
-
|
|
49
|
-
A parsed or deserialized AST crossing a trust boundary (an RPC payload, a
|
|
50
|
-
cached document) can be checked without throwing:
|
|
41
|
+
// '<h1>Hi</h1><p>Read the <a href="./guide.md">guide</a> for more, <em>thanks</em>.</p>'
|
|
51
42
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
isMarkdownNode({ element: 'text', value: 'hi' }) // true
|
|
56
|
-
isMarkdownNode({ element: 'bogus' }) // false
|
|
43
|
+
// …and back the other way.
|
|
44
|
+
renderMarkdown(htmlToMarkdown(parseHTML('<h1>Release notes</h1><p>Ship <b>fast</b>.</p>')))
|
|
45
|
+
// '# Release notes\n\nShip **fast**.'
|
|
57
46
|
```
|
|
58
47
|
|
|
59
|
-
`
|
|
60
|
-
|
|
61
|
-
|
|
48
|
+
`createMarkdown` (or `new Markdown`) runs a two-phase parse — block phase, then inline phase — and
|
|
49
|
+
holds the result as a stateful workspace exposing `find` / `filter` / `map` / `reduce` / `fold` /
|
|
50
|
+
`stream` / iteration over the AST. `renderHTML` projects that AST onto html's AST, sanitizes it
|
|
51
|
+
against a floor no option can lower, and serializes it. `renderMarkdown` writes canonical markdown
|
|
52
|
+
source instead. `htmlToMarkdown` folds an HTML AST back down to a `MarkdownDocument`, re-sanitizing
|
|
53
|
+
every destination as it goes. Guards (`isMarkdownNode`, `isMarkdownDocument`, `isBlockNode`,
|
|
54
|
+
`isInlineNode`) validate an AST that arrives from outside — an RPC payload, a cached document —
|
|
55
|
+
without ever throwing, and the non-recursive leaf nodes each compile to a `@orkestrel/contract`
|
|
56
|
+
bundle of guard, parser, JSON Schema, and seeded generator.
|
|
62
57
|
|
|
63
|
-
##
|
|
58
|
+
## Laws
|
|
64
59
|
|
|
65
|
-
|
|
66
|
-
`
|
|
67
|
-
|
|
68
|
-
|
|
60
|
+
- **Markdown fixpoint** — `parseDocument(renderMarkdown(document))` deep-equals a parser-produced
|
|
61
|
+
`document`, and `renderMarkdown` is idempotent.
|
|
62
|
+
- **Projection anchor** — `parseDocument(renderMarkdown(htmlToMarkdown(x)))` deep-equals
|
|
63
|
+
`htmlToMarkdown(x)`: whatever the projection emits, markdown can write it and read it back
|
|
64
|
+
unchanged.
|
|
69
65
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const text = createTextContract()
|
|
74
|
-
text.schema // the compiled JSON Schema
|
|
75
|
-
text.generate() // a seeded, schema-valid TextNode
|
|
76
|
-
```
|
|
66
|
+
Both round-trip laws hold within the depth budget — markdown's `MAX_DEPTH`, plus html's own cap on
|
|
67
|
+
the inbound trip; beyond it only totality is promised.
|
|
77
68
|
|
|
78
|
-
|
|
69
|
+
- **Sanitized output** — `renderHTML` refuses `javascript:`, `data:`, `vbscript:`, `file:`, and
|
|
70
|
+
protocol-relative destinations, removes unsafe subtrees whole, and strips every handler and
|
|
71
|
+
styling attribute, whatever the input AST claims.
|
|
79
72
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
- All of `renderHTML`'s rendered text and attributes are HTML-escaped.
|
|
84
|
-
`renderMarkdown` is not an HTML boundary — it writes markdown source, not
|
|
85
|
-
markup — so no HTML-escaping applies there.
|
|
86
|
-
- Parsing and rendering are depth-capped (`MAX_DEPTH`); past that depth the
|
|
87
|
-
parser/writer degrades to literal text instead of recursing further or
|
|
88
|
-
throwing.
|
|
73
|
+
HTML is richer than markdown, so what survives the inbound trip is the projected AST, not the input
|
|
74
|
+
bytes: comments and doctypes vanish, an element with no markdown meaning unwraps to its children,
|
|
75
|
+
and block content inside a table cell flattens to one line.
|
|
89
76
|
|
|
90
77
|
## Guide
|
|
91
78
|
|
|
92
|
-
For the full surface — the AST shape, the two-phase parse,
|
|
93
|
-
the contract-backed leaf shapes — see
|
|
94
|
-
[`guides/src/markdown.md`](guides/src/markdown.md).
|
|
79
|
+
For the full surface — the AST shape, the two-phase parse, the sanitization policy, the projection
|
|
80
|
+
seam, and the contract-backed leaf shapes — see [`guides/src/markdown.md`](guides/src/markdown.md).
|
|
95
81
|
|
|
96
82
|
## Package
|
|
97
83
|
|
|
98
|
-
Published as a single typed entry point per the `exports` field in
|
|
99
|
-
`package.json`.
|
|
84
|
+
Published as a single typed entry point per the `exports` field in `package.json`.
|
|
100
85
|
|
|
101
86
|
## License
|
|
102
87
|
|