@jarenjs/md 0.34.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.
Files changed (49) hide show
  1. package/README.md +520 -0
  2. package/dist/types/ast.d.ts +181 -0
  3. package/dist/types/bake.d.ts +61 -0
  4. package/dist/types/compiler.d.ts +141 -0
  5. package/dist/types/component/index.d.ts +101 -0
  6. package/dist/types/directives.d.ts +126 -0
  7. package/dist/types/entities.d.ts +40 -0
  8. package/dist/types/footnotes.d.ts +83 -0
  9. package/dist/types/frontmatter.d.ts +67 -0
  10. package/dist/types/html.d.ts +72 -0
  11. package/dist/types/index.d.ts +30 -0
  12. package/dist/types/loader.d.ts +84 -0
  13. package/dist/types/mdx.d.ts +45 -0
  14. package/dist/types/parser.d.ts +116 -0
  15. package/dist/types/plugins/highlight.d.ts +64 -0
  16. package/dist/types/plugins/index.d.ts +64 -0
  17. package/dist/types/plugins/mermaid.d.ts +12 -0
  18. package/dist/types/scanner.d.ts +240 -0
  19. package/dist/types/to-html.d.ts +104 -0
  20. package/dist/types/to-md.d.ts +23 -0
  21. package/dist/types/to-vnode.d.ts +161 -0
  22. package/dist/types/utils.d.ts +63 -0
  23. package/docs/LOADER.md +92 -0
  24. package/docs/MD-FORMAT.md +502 -0
  25. package/docs/PLUGINS.md +277 -0
  26. package/package.json +80 -0
  27. package/schemas/jaren-md-ast.schema.json +296 -0
  28. package/src/ast.js +346 -0
  29. package/src/bake.js +104 -0
  30. package/src/compiler.js +167 -0
  31. package/src/component/index.js +191 -0
  32. package/src/directives.js +371 -0
  33. package/src/entities.js +107 -0
  34. package/src/footnotes.js +180 -0
  35. package/src/frontmatter.js +947 -0
  36. package/src/html.js +281 -0
  37. package/src/index.js +76 -0
  38. package/src/loader.js +0 -0
  39. package/src/mdx.js +219 -0
  40. package/src/parser.js +1685 -0
  41. package/src/plugins/highlight.js +325 -0
  42. package/src/plugins/index.js +75 -0
  43. package/src/plugins/mermaid.js +14 -0
  44. package/src/scanner.js +832 -0
  45. package/src/to-html.js +425 -0
  46. package/src/to-md.js +396 -0
  47. package/src/to-vnode.js +766 -0
  48. package/src/utils.js +107 -0
  49. package/styles/md.css +238 -0
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @file GFM footnotes: which definitions a document actually cites, in
3
+ * which order, and what to call them.
4
+ *
5
+ * Definitions stay where the author wrote them (the AST is the
6
+ * document, not the rendering), so both emitters need the same answer to
7
+ * the same three questions before they emit anything: which definitions
8
+ * are cited, what number each one gets, and which identifiers the
9
+ * reference and its back-references carry. That answer lives here once —
10
+ * two emitters minting ids from two implementations is exactly how a
11
+ * back-reference ends up pointing at nothing.
12
+ */
13
+ export type MdNode = import('./ast.js').MdNode;
14
+ export type Footnotes = {
15
+ defs: MdNode[];
16
+ numbers: Map<string, number>;
17
+ counts: Map<string, number>;
18
+ refs: WeakMap<MdNode, {
19
+ number: number;
20
+ occurrence: number;
21
+ }>;
22
+ };
23
+ /**
24
+ * @typedef {import('./ast.js').MdNode} MdNode
25
+ */
26
+ /**
27
+ * @typedef {{ defs: MdNode[],
28
+ * numbers: Map<string, number>,
29
+ * counts: Map<string, number>,
30
+ * refs: WeakMap<MdNode, { number: number, occurrence: number }> }} Footnotes
31
+ */
32
+ /**
33
+ * The default identifier prefix for footnotes — GitHub's own.
34
+ *
35
+ * Unlike a heading id (MD-FORMAT.md §4.5), which defaults to no prefix
36
+ * because CommonMark asserts a bare heading and the primary consumer is
37
+ * a document the host wrote, a footnote id is emitted by the DEFAULT
38
+ * rendering of a feature whose whole point is a link between two places
39
+ * on one page. No spec example asserts a bare `fn-1`, so the safe
40
+ * default costs nothing and a host page keeps its own `#fn-1` for
41
+ * itself. `slugPrefix`, when given, replaces it.
42
+ */
43
+ export declare const FOOTNOTE_PREFIX = "user-content-";
44
+ /** The back-reference glyph GitHub uses. */
45
+ export declare const BACKREF_MARK = "\u21A9";
46
+ /**
47
+ * Collect a document's cited footnotes.
48
+ *
49
+ * Returns `null` when the AST holds no `footnoteDefinition` at all — the
50
+ * overwhelmingly common case, and the one that must cost nothing: the
51
+ * emitters compare that `null` to decide whether anything about footnote
52
+ * rendering can differ between two emissions.
53
+ *
54
+ * The result is memoized on the AST array, so re-rendering one document
55
+ * reuses it (and with it the vnode emitter's per-node memo); a
56
+ * transformed document is a different array and correctly gets fresh
57
+ * numbering.
58
+ *
59
+ * @param {MdNode[]} ast
60
+ * @returns {Footnotes|null}
61
+ */
62
+ export declare function collectFootnotes(ast: MdNode[]): Footnotes | null;
63
+ /**
64
+ * The `id` of a rendered footnote (the `<li>` in the section).
65
+ * @param {string} prefix @param {number} number
66
+ * @returns {string}
67
+ */
68
+ export declare function footnoteId(prefix: string, number: number): string;
69
+ /**
70
+ * The `id` of one citation. A footnote cited more than once needs one
71
+ * landing place per citation, or every back-reference would return the
72
+ * reader to the first.
73
+ * @param {string} prefix @param {number} number @param {number} occurrence
74
+ * @returns {string}
75
+ */
76
+ export declare function footnoteRefId(prefix: string, number: number, occurrence: number): string;
77
+ /**
78
+ * The accessible name of a back-reference: `↩` alone names nothing, and
79
+ * a footnote with several of them needs them told apart.
80
+ * @param {number} number @param {number} occurrence
81
+ * @returns {string}
82
+ */
83
+ export declare function backrefLabel(number: number, occurrence: number): string;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * @file Frontmatter extraction: YAML subset, JSON and TOML → plain JSON.
3
+ *
4
+ * Frontmatter is detected at the very top of the source only:
5
+ *
6
+ * - `---` opens a YAML-subset block, closed by `---` or `...`
7
+ * - `---json` opens a JSON block, closed by `---`
8
+ * - `{` (as the first character) opens a JSON object closed by a
9
+ * line that is exactly `}`
10
+ * - `+++` opens a TOML block, closed by `+++`
11
+ *
12
+ * Whatever the syntax, the result normalizes to one plain JSON value on
13
+ * the document. The parsers are written from scratch and dependency-free;
14
+ * an external TOML parser (e.g. `parseToml` from `@jarenjs/josl`) can be
15
+ * injected through `options.toml` to replace the built-in TOML subset.
16
+ *
17
+ * The YAML subset (normative limits in docs/MD-FORMAT.md §3):
18
+ * scalars (null/booleans/numbers/strings), single- and double-quoted
19
+ * strings, block maps and sequences by indentation, flow arrays and
20
+ * maps (multi-line while brackets are open), literal `|` and folded `>`
21
+ * block scalars with `-` chomping, and `#` comments. No anchors, no
22
+ * aliases, no tags, no multi-document streams, no complex keys.
23
+ */
24
+ /** Raised for malformed frontmatter inside a detected fence. */
25
+ export declare class MdFrontmatterError extends Error {
26
+ line: number;
27
+ /**
28
+ * @param {string} message
29
+ * @param {number} line 0-based line index inside the frontmatter block
30
+ */
31
+ constructor(message: string, line: number);
32
+ }
33
+ /**
34
+ * Split frontmatter off the top of a Markdown source.
35
+ *
36
+ * @param {string} source
37
+ * @param {{ toml?: (text: string) => any }} [options]
38
+ * @returns {{ data: any, body: string, lang: 'yaml'|'json'|'toml'|null }}
39
+ */
40
+ export declare function parseFrontmatter(source: string, options?: {
41
+ toml?: (text: string) => any;
42
+ }): {
43
+ data: any;
44
+ body: string;
45
+ lang: 'yaml' | 'json' | 'toml' | null;
46
+ };
47
+ /**
48
+ * Parse the YAML subset into plain JSON.
49
+ * @param {string} text
50
+ * @returns {any}
51
+ */
52
+ export declare function parseYamlSubset(text: string): any;
53
+ export type YamlState = {
54
+ lines: string[];
55
+ pos: number;
56
+ };
57
+ /**
58
+ * Parse the built-in TOML subset: `[table]` and `[[array-of-tables]]`
59
+ * headers with dotted paths, bare/quoted/dotted keys, basic and literal
60
+ * strings, integers (decimal/hex/octal/binary, `_` separators), floats,
61
+ * booleans, single- or multi-line flow arrays, inline tables, and `#`
62
+ * comments. Datetimes are kept as strings; multi-line strings are not
63
+ * supported (normative limits in docs/MD-FORMAT.md §3.3).
64
+ * @param {string} text
65
+ * @returns {Record<string, any>}
66
+ */
67
+ export declare function parseTomlSubset(text: string): Record<string, any>;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @file Raw HTML in Markdown, parsed to vnodes through an allow-list.
3
+ *
4
+ * The vnode format has no unescaped output — deliberately: a tree of
5
+ * arrays cannot carry a half-open tag or an injected `<script>`, which
6
+ * is why `mdToVnode` has always either dropped raw HTML or shown it as
7
+ * text. This module is the third answer: PARSE the HTML and keep what an
8
+ * allow-list recognises, so `<details>` and `<img>` in a document render
9
+ * as themselves.
10
+ *
11
+ * The safety argument is structural rather than a promise about
12
+ * filtering strings. The output is a vnode tree, so:
13
+ *
14
+ * - an element not on the list contributes nothing but its children's
15
+ * text — there is no path by which its markup reaches the DOM;
16
+ * - an attribute not on the list is dropped, so `on*` handlers,
17
+ * `style` and `srcdoc` never exist to begin with;
18
+ * - `href`/`src` go through the same {@link sanitizeUrl} policy as
19
+ * Markdown's own links, so `javascript:` cannot ride in on raw HTML
20
+ * when it cannot ride in on `[x](…)`.
21
+ *
22
+ * What this is NOT: an HTML5 parser. It does not implement implicit
23
+ * end tags, foster parenting, or the tokenizer's error recovery, and it
24
+ * does not attempt to reproduce what a browser would build from
25
+ * malformed input. Unbalanced input closes at the end of the fragment.
26
+ * For hostile input the goal is that nothing survives that should not,
27
+ * not that the shape matches what a browser would have made of it.
28
+ *
29
+ * A host that wants different rules injects its own parser instead
30
+ * (`mdToVnode`'s `parseHtml` option) — this is the default, not the
31
+ * only, implementation.
32
+ */
33
+ export type HtmlParseOptions = {
34
+ /**
35
+ * URL policy for
36
+ * `href`/`src` (default: the shared deny-list, as Markdown links use)
37
+ */
38
+ sanitizeUrl?: (url: string) => (string | null);
39
+ };
40
+ /**
41
+ * @typedef {object} HtmlParseOptions
42
+ * @property {(url: string) => (string|null)} [sanitizeUrl] URL policy for
43
+ * `href`/`src` (default: the shared deny-list, as Markdown links use)
44
+ */
45
+ /**
46
+ * Parse an HTML fragment into vnodes, keeping only what the allow-list
47
+ * recognises.
48
+ *
49
+ * @param {string} source the raw HTML of one Markdown html node
50
+ * @param {HtmlParseOptions} [options]
51
+ * @returns {any[]} vnodes and strings; empty when nothing survived
52
+ * @example
53
+ * parseHtmlFragment('<details><summary>More</summary>text</details>');
54
+ * // [['details', {}, ['summary', {}, 'More'], 'text']]
55
+ */
56
+ export declare function parseHtmlFragment(source: string, options?: HtmlParseOptions): any[];
57
+ /**
58
+ * Classify ONE tag — what CommonMark's inline phase hands out, since at
59
+ * that level a tag is not an element (`<b>bold</b>` is three siblings).
60
+ * Returns null when the source is not exactly one tag.
61
+ * @param {string} source
62
+ * @param {HtmlParseOptions} [options]
63
+ * @returns {{name: string, closing: boolean, complete: boolean,
64
+ * drop: boolean, props: Record<string, any>|null} | null}
65
+ */
66
+ export declare function parseHtmlTag(source: string, options?: HtmlParseOptions): {
67
+ name: string;
68
+ closing: boolean;
69
+ complete: boolean;
70
+ drop: boolean;
71
+ props: Record<string, any> | null;
72
+ } | null;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @jarenjs/md - Markdown + frontmatter as JSON documents.
3
+ *
4
+ * parseMarkdown turns Markdown (CommonMark core + GFM tables,
5
+ * strikethrough, task lists, footnotes and autolink literals +
6
+ * YAML/JSON/TOML frontmatter) into a plain
7
+ * JSON AST the rest of the suite consumes natively: JSLT/JTLT and
8
+ * query documents transform it, `mdToVnode` projects it to
9
+ * @jarenjs/view vnodes with content-hash keys, `toMarkdown` prints
10
+ * canonical round-trip text, and `loadMarkdown`/`streamMarkdown` pull
11
+ * documents lazily from any URL with caching, AbortSignal and
12
+ * block-by-block streaming. Extensibility is compile-time plugins
13
+ * (`definePlugin`, `@jarenjs/md/plugins`) baked into dispatch tables.
14
+ *
15
+ * The normative contracts: docs/MD-FORMAT.md (AST + frontmatter),
16
+ * docs/PLUGINS.md (plugin system), docs/LOADER.md (loader).
17
+ */
18
+ export { parseMarkdown, createIncrementalParser, buildPluginTables, } from './parser.js';
19
+ export { compileMarkdown, frontmatterExternals, mdToForm, } from './compiler.js';
20
+ export { loadMarkdown, streamMarkdown, createMdCache, defaultMdCache, } from './loader.js';
21
+ export { toMarkdown } from './to-md.js';
22
+ export { toHtml } from './to-html.js';
23
+ export { mdToVnode, createMdRenderer, } from './to-vnode.js';
24
+ export { scanDirectives, replaceDirectives, scanSourceDirectives, parseMarker, } from './directives.js';
25
+ export { bake } from './bake.js';
26
+ export { definePlugin } from './plugins/index.js';
27
+ export { parseHtmlFragment, parseHtmlTag } from './html.js';
28
+ export { MD_VERSION, walkAst, visitAst, textOf, isContainerNode, } from './ast.js';
29
+ export { parseFrontmatter, parseYamlSubset, parseTomlSubset, MdFrontmatterError, } from './frontmatter.js';
30
+ export { hashContent } from './utils.js';
@@ -0,0 +1,84 @@
1
+ /**
2
+ * @file The lazy URL loader (docs/LOADER.md): cache, abort, streaming.
3
+ *
4
+ * `loadMarkdown` resolves any URL the platform `fetch` accepts into a
5
+ * compiled document. The shared LRU cache keys on normalized URL +
6
+ * plugin set, remembers `ETag`/`Last-Modified` validators, shares
7
+ * in-flight fetches, and revalidates stale entries in the background.
8
+ * When the response body is a `ReadableStream` the parser runs
9
+ * block-by-block as chunks arrive — `streamMarkdown` exposes that as
10
+ * an async iterator of completed top-level blocks.
11
+ */
12
+ export type MdNode = import('./ast.js').MdNode;
13
+ export type MdDocument = import('./ast.js').MdDocument;
14
+ export type CompiledMd = import('./compiler.js').CompiledMd;
15
+ export type MdCompileOptions = import('./compiler.js').MdCompileOptions;
16
+ export type MdLoadOptions = MdCompileOptions & {
17
+ base?: string | URL;
18
+ signal?: AbortSignal;
19
+ cache?: MdCache | false;
20
+ fetch?: typeof globalThis.fetch;
21
+ };
22
+ export type MdCache = {
23
+ get: (key: string) => any;
24
+ set: (key: string, entry: any) => void;
25
+ delete: (key: string) => boolean;
26
+ clear: () => void;
27
+ };
28
+ /**
29
+ * @typedef {import('./ast.js').MdNode} MdNode
30
+ * @typedef {import('./ast.js').MdDocument} MdDocument
31
+ * @typedef {import('./compiler.js').CompiledMd} CompiledMd
32
+ * @typedef {import('./compiler.js').MdCompileOptions} MdCompileOptions
33
+ */
34
+ /**
35
+ * @typedef {MdCompileOptions & {
36
+ * base?: string | URL,
37
+ * signal?: AbortSignal,
38
+ * cache?: MdCache | false,
39
+ * fetch?: typeof globalThis.fetch,
40
+ * }} MdLoadOptions
41
+ */
42
+ /**
43
+ * @typedef {object} MdCache
44
+ * @property {(key: string) => any} get
45
+ * @property {(key: string, entry: any) => void} set
46
+ * @property {(key: string) => boolean} delete
47
+ * @property {() => void} clear
48
+ */
49
+ /**
50
+ * A small LRU cache (Map re-insertion order).
51
+ * @param {number} [limit]
52
+ * @returns {MdCache}
53
+ */
54
+ export declare function createMdCache(limit?: number): MdCache;
55
+ /** The shared default cache instance. */
56
+ export declare const defaultMdCache: MdCache;
57
+ /**
58
+ * Load and compile a Markdown document from a URL (docs/LOADER.md §1).
59
+ *
60
+ * @example
61
+ * const md = await loadMarkdown('/docs/intro.md', { base: location.href });
62
+ * md.frontmatter; md.toVnode();
63
+ *
64
+ * @param {string | URL} url
65
+ * @param {MdLoadOptions} [options]
66
+ * @returns {Promise<CompiledMd>}
67
+ */
68
+ export declare function loadMarkdown(url: string | URL, options?: MdLoadOptions): Promise<CompiledMd>;
69
+ /**
70
+ * Stream a Markdown source, yielding completed top-level block nodes
71
+ * as their end becomes certain; the generator's return value is the
72
+ * finished MdDocument (docs/LOADER.md §4).
73
+ *
74
+ * Accepts a URL, a `Response`, a `ReadableStream`, or any (async)
75
+ * iterable of string/Uint8Array chunks.
76
+ *
77
+ * @example
78
+ * for await (const block of streamMarkdown('/big.md')) render(block);
79
+ *
80
+ * @param {any} urlOrStream
81
+ * @param {MdLoadOptions} [options]
82
+ * @returns {AsyncGenerator<MdNode, MdDocument>}
83
+ */
84
+ export declare function streamMarkdown(urlOrStream: any, options?: MdLoadOptions): AsyncGenerator<MdNode, MdDocument>;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @file mdx = markdown × data. A markdown document renders DYNAMICALLY
3
+ * against a data document: inline interpolation, conditional sections
4
+ * and repeated sections, all driven by the data — still a pure
5
+ * `(doc, data) → doc` transform over the parsed AST, so everything
6
+ * downstream (`mdToVnode`, `toMarkdown`, the plugins) works unchanged.
7
+ *
8
+ * The template vocabulary reuses the suite's own query expressions —
9
+ * no new mini-language:
10
+ *
11
+ * - `{$.path}` inline in TEXT interpolates a query expression over the
12
+ * data (`$` is the data document; `$name` externals come from the
13
+ * frontmatter and the enclosing `each` bindings). Code spans, code
14
+ * blocks and raw HTML never interpolate.
15
+ * - a paragraph of exactly `{#if <expr>}` … `{/if}` keeps its section
16
+ * only when the expression is truthy (the query engine's boolean
17
+ * view: null/false/''/0/[] are false).
18
+ * - a paragraph of exactly `{#each <expr> as <name>}` … `{/each}`
19
+ * repeats its section once per item, binding each as the external
20
+ * `$<name>`. Sections nest.
21
+ *
22
+ * A directive must form its OWN paragraph — surround it with blank
23
+ * lines, or markdown's lazy continuation folds the next line into it
24
+ * and the directive reads as plain text.
25
+ *
26
+ * The expression COMPILER is injected (`compileJsonQuery` from
27
+ * `@jarenjs/json`), so this package's engine layer keeps its
28
+ * core+view-only dependency contract — the same seam philosophy the
29
+ * play surface uses for its renderers.
30
+ */
31
+ /**
32
+ * Build the mdx transformer around an injected expression compiler.
33
+ * @param {{ compileQuery: (expr: string) => (data: any, externals?: any) => any }} options
34
+ * `compileQuery` compiles one query expression (e.g. `compileJsonQuery`
35
+ * from `@jarenjs/json`); compiled expressions are cached per source text
36
+ * @returns {{ transform: (doc: any, data: any) => any }}
37
+ * `transform(doc, data)` — a parsed markdown doc + a data document in,
38
+ * a NEW doc (directives resolved, text interpolated) out; never throws
39
+ * on a bad expression — the error message renders in place, honestly
40
+ */
41
+ export declare function createMdx(options: {
42
+ compileQuery: (expr: string) => (data: any, externals?: any) => any;
43
+ }): {
44
+ transform: (doc: any, data: any) => any;
45
+ };
@@ -0,0 +1,116 @@
1
+ /**
2
+ * @file The Markdown parser: block structure + inline parsing → AST.
3
+ *
4
+ * One pass over the source builds the block tree (a container stack of
5
+ * blockquotes/lists/items plus one open leaf), buffering each leaf's
6
+ * raw text; inline parsing runs once per leaf when it closes. Plugin
7
+ * extension points — fence claims, block rules, inline rules — are
8
+ * prebuilt tables consulted by indexed lookup in the hot loop
9
+ * (docs/PLUGINS.md); with no plugins the tables are shared empty maps.
10
+ *
11
+ * The same machinery runs batch (`parseMarkdown`) and incrementally
12
+ * (`createIncrementalParser`): blocks land in the output only when
13
+ * closed, so the incremental parser can hand out completed top-level
14
+ * blocks while later chunks are still arriving (docs/LOADER.md §4).
15
+ */
16
+ export type MdNode = import('./ast.js').MdNode;
17
+ export type MdDocument = import('./ast.js').MdDocument;
18
+ export type MdParseOptions = {
19
+ /**
20
+ * compiled-in plugins (docs/PLUGINS.md)
21
+ */
22
+ plugins?: any[];
23
+ /**
24
+ * GFM tables/strikethrough/task lists (default true)
25
+ */
26
+ gfm?: boolean;
27
+ /**
28
+ * detect frontmatter (default true)
29
+ */
30
+ frontmatter?: boolean;
31
+ /**
32
+ * injectable TOML frontmatter parser
33
+ */
34
+ toml?: (text: string) => any;
35
+ /**
36
+ * recorded in `meta.sourceUrl`
37
+ */
38
+ sourceUrl?: string | null;
39
+ };
40
+ /**
41
+ * Merge a plugin array into the four dispatch tables (first plugin
42
+ * wins on every collision). Memoized by array identity so module-level
43
+ * plugin arrays compile exactly once.
44
+ * @param {any[] | undefined} plugins
45
+ */
46
+ export declare function buildPluginTables(plugins: any[] | undefined): any;
47
+ export type InlineCtx = {
48
+ defs: Map<string, {
49
+ url: string;
50
+ title: string | null;
51
+ }>;
52
+ footnotes?: Map<string, MdNode>;
53
+ inlines: Map<number, any[]>;
54
+ gfm: boolean;
55
+ ctx: any;
56
+ unresolved?: boolean;
57
+ deferred?: {
58
+ node: any;
59
+ raw: string;
60
+ }[];
61
+ };
62
+ /**
63
+ * The inline parsing context threaded through one document.
64
+ * @typedef {{ defs: Map<string, {url: string, title: string|null}>,
65
+ * footnotes?: Map<string, MdNode>,
66
+ * inlines: Map<number, any[]>, gfm: boolean, ctx: any,
67
+ * unresolved?: boolean, deferred?: {node: any, raw: string}[] }} InlineCtx
68
+ */
69
+ /**
70
+ * Parse inline Markdown text into inline AST nodes.
71
+ * @param {string} src
72
+ * @param {InlineCtx} ictx
73
+ * @returns {MdNode[]}
74
+ */
75
+ export declare function parseInlines(src: string, ictx: InlineCtx): MdNode[];
76
+ /**
77
+ * Resolve the buffered raw text of finished blocks into inline
78
+ * children (paragraphs, headings, table cells, task-list markers).
79
+ * Runs once per block, after which the `raw` buffers are gone.
80
+ * @param {MdNode[]} blocks
81
+ * @param {InlineCtx} ictx
82
+ */
83
+ export declare function finishBlocks(blocks: MdNode[], ictx: InlineCtx): void;
84
+ /**
85
+ * Parse Markdown source into an MdDocument (frontmatter + AST).
86
+ *
87
+ * @example
88
+ * parseMarkdown('# Hi').ast
89
+ * // [{ type: 'heading', depth: 1, children: [{ type: 'text', value: 'Hi' }] }]
90
+ *
91
+ * @param {string} source
92
+ * @param {MdParseOptions} [options]
93
+ * @returns {MdDocument}
94
+ */
95
+ export declare function parseMarkdown(source: string, options?: MdParseOptions): MdDocument;
96
+ /**
97
+ * The incremental parsing core behind `streamMarkdown` (docs/LOADER.md
98
+ * §4): feed chunks, collect completed top-level blocks per feed, and
99
+ * flush the tail with `end()`.
100
+ *
101
+ * Frontmatter resolves as soon as its closing fence arrives; reference
102
+ * definitions apply to blocks completed after them (the documented
103
+ * streaming limitation).
104
+ *
105
+ * @param {MdParseOptions} [options]
106
+ * @returns {{
107
+ * feed: (chunk: string) => MdNode[],
108
+ * end: () => MdDocument,
109
+ * frontmatter: any,
110
+ * }}
111
+ */
112
+ export declare function createIncrementalParser(options?: MdParseOptions): {
113
+ feed: (chunk: string) => MdNode[];
114
+ end: () => MdDocument;
115
+ frontmatter: any;
116
+ };
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @file Syntax highlighting for fenced code blocks (PLUGINS.md §6.2).
3
+ *
4
+ * Built-in mode is a zero-dependency single-pass tokenizer driven by
5
+ * compact grammar tables — a keyword set, comment/string delimiters
6
+ * and punctuation classes — compiled to closures once at module load.
7
+ * No regex runs in the token loop. Adapter mode puts shiki/prism/
8
+ * highlight.js behind the same `{ kind, value }` token contract.
9
+ *
10
+ * Token kinds: kw str num com pun id op lit — rendered as
11
+ * `span.tok-{kind}` (plain `id` runs render as bare text).
12
+ */
13
+ export type Token = {
14
+ kind: 'kw' | 'str' | 'num' | 'com' | 'pun' | 'id' | 'op' | 'lit';
15
+ value: string;
16
+ };
17
+ export type MdGrammar = {
18
+ keywords?: string[];
19
+ literals?: string[];
20
+ /**
21
+ * comment-to-end-of-line prefixes
22
+ */
23
+ lineComments?: string[];
24
+ /**
25
+ * open/close pair
26
+ */
27
+ blockComment?: [string, string];
28
+ /**
29
+ * string delimiter characters
30
+ */
31
+ strings?: string;
32
+ /**
33
+ * recognize number literals
34
+ */
35
+ numbers?: boolean;
36
+ /**
37
+ * extra identifier characters (e.g. `-` for CSS)
38
+ */
39
+ extraId?: string;
40
+ };
41
+ /** The canonical names of the shipped grammars (aliases resolve too). */
42
+ export declare const GRAMMAR_NAMES: readonly string[];
43
+ /**
44
+ * Tokenize `code` with the built-in grammar for `lang`. Returns null
45
+ * when no grammar covers the language.
46
+ * @param {string} code
47
+ * @param {string|null} lang
48
+ * @returns {Token[] | null}
49
+ */
50
+ export declare function tokenizeCode(code: string, lang: string | null): Token[] | null;
51
+ /**
52
+ * The syntax-highlighting plugin: takes over rendering of `code`
53
+ * nodes. `adapter(code, lang)` may return tokens (shiki/prism/hljs
54
+ * behind the shared contract) or null to fall back to the built-in
55
+ * grammars, then to plain text.
56
+ *
57
+ * @param {{ grammars?: Record<string, MdGrammar>,
58
+ * adapter?: (code: string, lang: string|null) => (Token[] | null) }} [config]
59
+ * @returns {import('./index.js').MdPlugin}
60
+ */
61
+ export declare function highlightPlugin(config?: {
62
+ grammars?: Record<string, MdGrammar>;
63
+ adapter?: (code: string, lang: string | null) => (Token[] | null);
64
+ }): import('./index.js').MdPlugin;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @file Plugin registry helpers and the reference plugins.
3
+ *
4
+ * A plugin is data, validated and frozen at definition time; the
5
+ * parser and vnode emitter bake plugin arrays into dispatch tables at
6
+ * compile time (the normative contract lives in docs/PLUGINS.md).
7
+ */
8
+ export type MdPlugin = {
9
+ /**
10
+ * unique kebab-case identifier
11
+ */
12
+ name: string;
13
+ /**
14
+ * fenced-code claims by info-string first word
15
+ */
16
+ fences?: string[];
17
+ /**
18
+ * block rule descriptors ({ chars, start, continue, close })
19
+ */
20
+ blocks?: any[];
21
+ /**
22
+ * inline rule descriptors ({ char, scan })
23
+ */
24
+ inlines?: any[];
25
+ /**
26
+ * the AST type this plugin emits/renders
27
+ */
28
+ node?: string;
29
+ /**
30
+ * pure vnode renderer
31
+ */
32
+ render?: (node: any, h: any, ctx: any) => any;
33
+ /**
34
+ * pure HTML-string
35
+ * renderer, for `toHtml`. Independent of `render`: a plugin may serve
36
+ * one emitter, the other, or both (docs/PLUGINS.md §5.1)
37
+ */
38
+ toHtml?: (node: any, ctx: any) => string;
39
+ /**
40
+ * browser-only upgrade
41
+ */
42
+ hydrate?: (el: any, node: any, ctx: any) => any;
43
+ };
44
+ /**
45
+ * @typedef {object} MdPlugin
46
+ * @property {string} name unique kebab-case identifier
47
+ * @property {string[]} [fences] fenced-code claims by info-string first word
48
+ * @property {any[]} [blocks] block rule descriptors ({ chars, start, continue, close })
49
+ * @property {any[]} [inlines] inline rule descriptors ({ char, scan })
50
+ * @property {string} [node] the AST type this plugin emits/renders
51
+ * @property {(node: any, h: any, ctx: any) => any} [render] pure vnode renderer
52
+ * @property {(node: any, ctx: any) => string} [toHtml] pure HTML-string
53
+ * renderer, for `toHtml`. Independent of `render`: a plugin may serve
54
+ * one emitter, the other, or both (docs/PLUGINS.md §5.1)
55
+ * @property {(el: any, node: any, ctx: any) => any} [hydrate] browser-only upgrade
56
+ */
57
+ /**
58
+ * Validate and freeze a plugin spec (docs/PLUGINS.md §1).
59
+ * @param {MdPlugin} spec
60
+ * @returns {MdPlugin}
61
+ */
62
+ export declare function definePlugin(spec: MdPlugin): MdPlugin;
63
+ export { highlightPlugin, tokenizeCode, GRAMMAR_NAMES } from './highlight.js';
64
+ export { mermaidPlugin } from './mermaid.js';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @file Mermaid diagram support (PLUGINS.md §6.1).
3
+ *
4
+ * The **native** plugin from `@jarenjs/mermaid`: a self-frozen
5
+ * `MdPlugin`-shaped object whose `render` parses the fence source and
6
+ * emits pure-vnode SVG synchronously (SSR-safe, no injected `mermaid`
7
+ * instance, no `innerHTML`). The dependency arrow points md → mermaid
8
+ *, and `@jarenjs/mermaid/plugin` does not import
9
+ * `definePlugin`, so there is no cycle. Consumers who never use it
10
+ * tree-shake it away (`sideEffects:false`).
11
+ */
12
+ export { mermaidPlugin, refreshMermaidFence } from '@jarenjs/mermaid/plugin';