@orkestrel/markdown 0.0.5 → 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 +2835 -1316
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +1822 -8
- package/dist/src/core/index.d.ts +1822 -8
- package/dist/src/core/index.js +2819 -1312
- package/dist/src/core/index.js.map +1 -1
- package/package.json +20 -15
- package/dist/src/core/Markdown.d.ts +0 -86
- package/dist/src/core/constants.d.ts +0 -17
- package/dist/src/core/factories.d.ts +0 -92
- package/dist/src/core/helpers.d.ts +0 -451
- package/dist/src/core/parsers.d.ts +0 -66
- package/dist/src/core/shapers.d.ts +0 -105
- package/dist/src/core/types.d.ts +0 -269
- package/dist/src/core/validators.d.ts +0 -287
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orkestrel/markdown",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.7",
|
|
4
|
+
"description": "A types-first markdown layer over @orkestrel/html — typed AST, both conversion directions, and sanitized HTML by default.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ast",
|
|
7
7
|
"commonmark",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"url": "git+https://github.com/orkestrel/markdown.git"
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
|
-
"dist",
|
|
23
|
+
"dist/src",
|
|
24
24
|
"README.md"
|
|
25
25
|
],
|
|
26
26
|
"type": "module",
|
|
@@ -45,19 +45,20 @@
|
|
|
45
45
|
"access": "public"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
|
-
"clean": "node -e \"
|
|
48
|
+
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
49
49
|
"copy": "node -e \"const fs=require('node:fs'),p=require('node:path'),a=process.argv[1],b=process.argv[2];fs.mkdirSync(p.dirname(b),{recursive:true});fs.cpSync(a,b,{force:true});console.log('Copied: '+a+' to '+b)\"",
|
|
50
|
-
"
|
|
51
|
-
"lint": "oxlint --config .oxlintrc.json --fix .",
|
|
50
|
+
"scaffold": "scaffold",
|
|
51
|
+
"lint": "oxlint --config .oxlintrc.json --fix --deny-warnings .",
|
|
52
52
|
"check": "tsc --noEmit --project tsconfig.json && npm run check:src",
|
|
53
53
|
"check:src": "npm run check:src:core",
|
|
54
54
|
"check:src:core": "tsc --noEmit -p configs/src/tsconfig.core.json",
|
|
55
55
|
"format": "oxfmt --config .oxfmtrc.json --write .",
|
|
56
56
|
"format:check": "oxfmt --config .oxfmtrc.json --check .",
|
|
57
|
-
"lint:check": "oxlint --config .oxlintrc.json .",
|
|
58
|
-
"test": "npm run test:src && npm run test:guides",
|
|
57
|
+
"lint:check": "oxlint --config .oxlintrc.json --deny-warnings .",
|
|
58
|
+
"test": "npm run test:src && npm run test:policy && npm run test:guides",
|
|
59
59
|
"test:src": "vitest run --config vite.config.ts --no-cache --reporter=dot --project src:core",
|
|
60
60
|
"test:src:core": "vitest run --config vite.config.ts --no-cache --reporter=dot --project src:core",
|
|
61
|
+
"test:policy": "vitest run --config vite.config.ts --no-cache --reporter=dot --project policy",
|
|
61
62
|
"test:guides": "vitest run --config vite.config.ts --reporter=dot --project guides",
|
|
62
63
|
"build": "npm run clean && npm run build:src",
|
|
63
64
|
"build:src": "npm run build:src:core",
|
|
@@ -65,19 +66,23 @@
|
|
|
65
66
|
"prepublishOnly": "npm run format:check && npm run lint:check && npm run check && npm run build && npm test"
|
|
66
67
|
},
|
|
67
68
|
"dependencies": {
|
|
68
|
-
"@orkestrel/contract": "^0.0.
|
|
69
|
+
"@orkestrel/contract": "^0.0.9",
|
|
70
|
+
"@orkestrel/html": "^0.0.1"
|
|
69
71
|
},
|
|
70
72
|
"devDependencies": {
|
|
71
|
-
"@
|
|
72
|
-
"@
|
|
73
|
-
"
|
|
74
|
-
"
|
|
73
|
+
"@microsoft/api-extractor": "^7.58.12",
|
|
74
|
+
"@orkestrel/guide": "^0.0.7",
|
|
75
|
+
"@orkestrel/scaffold": "^0.0.11",
|
|
76
|
+
"@types/node": "^26.1.2",
|
|
77
|
+
"@vitest/browser-playwright": "^4.1.10",
|
|
78
|
+
"oxfmt": "^0.61.0",
|
|
79
|
+
"oxlint": "^1.76.0",
|
|
75
80
|
"typescript": "^6.0.3",
|
|
76
|
-
"vite": "^8.
|
|
81
|
+
"vite": "^8.2.0",
|
|
77
82
|
"vite-plugin-dts": "^5.0.3",
|
|
78
83
|
"vitest": "^4.1.10"
|
|
79
84
|
},
|
|
80
85
|
"engines": {
|
|
81
|
-
"node": ">=22"
|
|
86
|
+
"node": ">=22.12.0"
|
|
82
87
|
}
|
|
83
88
|
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { BlockNode, MarkdownDocument, MarkdownHandlers, MarkdownInterface, MarkdownNode, MarkdownRewriteHandler } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* A stateful, parsed markdown document - wraps a typed {@link MarkdownDocument} AST
|
|
4
|
-
* with the query (`find` / `filter` / `reduce` / iteration), rewrite (`map`), fold, and
|
|
5
|
-
* streaming operations {@link MarkdownInterface} declares.
|
|
6
|
-
*
|
|
7
|
-
* @remarks
|
|
8
|
-
* - **Construction.** Given a `string`, the constructor runs {@link parseDocument} (the
|
|
9
|
-
* block phase then the inline phase) to build the AST. Given a {@link MarkdownDocument},
|
|
10
|
-
* the document is adopted AS-IS and is NOT re-validated - a caller adopting an
|
|
11
|
-
* untrusted value should gate it with `isMarkdownDocument` first.
|
|
12
|
-
* - **Immutable.** {@link map} never mutates the stored AST - it returns a NEW `Markdown`
|
|
13
|
-
* instance; the document root invariant (`element: 'document'`) always holds.
|
|
14
|
-
* - **Traversal order.** {@link walk} and the `find` / `filter` / `reduce` queries built
|
|
15
|
-
* on it walk the AST depth-first, pre-order, root-inclusive (via {@link walkNodes});
|
|
16
|
-
* `stream` is shallow - only the document's direct block children.
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* ```ts
|
|
20
|
-
* import { Markdown, isHeadingNode, renderMarkdown } from '@src/core'
|
|
21
|
-
*
|
|
22
|
-
* const markdown = new Markdown('# Title\n\nA **bold** [link](https://x.dev).')
|
|
23
|
-
* const heading = markdown.find(isHeadingNode) // the HeadingNode, or undefined
|
|
24
|
-
* const shouted = markdown.map((node) =>
|
|
25
|
-
* node.element === 'text' ? { element: 'text', value: node.value.toUpperCase() } : node,
|
|
26
|
-
* )
|
|
27
|
-
* renderMarkdown(shouted.document) // '# TITLE\n\nA **BOLD** [LINK](https://x.dev).'
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
export declare class Markdown implements MarkdownInterface {
|
|
31
|
-
#private;
|
|
32
|
-
constructor(input: string | MarkdownDocument);
|
|
33
|
-
/** The stored {@link MarkdownDocument} AST root. */
|
|
34
|
-
get document(): MarkdownDocument;
|
|
35
|
-
/**
|
|
36
|
-
* THE deep traversal - a lazy, depth-first, pre-order, root-inclusive generator
|
|
37
|
-
* over every {@link MarkdownNode} in the document. `find` / `filter` / `reduce`
|
|
38
|
-
* all iterate this single traversal.
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* ```ts
|
|
42
|
-
* for (const node of markdown.walk()) {
|
|
43
|
-
* // every node, depth-first, pre-order, root-inclusive
|
|
44
|
-
* }
|
|
45
|
-
*
|
|
46
|
-
* // also consumable by for-await - JS accepts a sync iterable in for-await
|
|
47
|
-
* for await (const node of markdown.walk()) {
|
|
48
|
-
* // same sequence, no separate async iterator needed
|
|
49
|
-
* }
|
|
50
|
-
* ```
|
|
51
|
-
*/
|
|
52
|
-
walk(): Generator<MarkdownNode>;
|
|
53
|
-
find<T extends MarkdownNode>(guard: (node: MarkdownNode) => node is T): T | undefined;
|
|
54
|
-
find(predicate: (node: MarkdownNode) => boolean): MarkdownNode | undefined;
|
|
55
|
-
filter<T extends MarkdownNode>(guard: (node: MarkdownNode) => node is T): readonly T[];
|
|
56
|
-
filter(predicate: (node: MarkdownNode) => boolean): readonly MarkdownNode[];
|
|
57
|
-
/** Rewrites the AST bottom-up (copy-on-write) and returns a new {@link Markdown}. */
|
|
58
|
-
map(rewrite: MarkdownRewriteHandler): MarkdownInterface;
|
|
59
|
-
/** Folds the AST depth-first, pre-order into an accumulator. */
|
|
60
|
-
reduce<T>(callback: (accumulator: T, node: MarkdownNode) => T, initial: T): T;
|
|
61
|
-
/** Runs a total catamorphism over the document using a {@link MarkdownHandlers} table. */
|
|
62
|
-
fold<T>(handlers: MarkdownHandlers<T>): T;
|
|
63
|
-
/**
|
|
64
|
-
* A web-standard {@link ReadableStream} over the document's top-level block nodes
|
|
65
|
-
* (shallow, source order) - a fresh, pull-based source per call: one block is
|
|
66
|
-
* enqueued per `pull`, so a slow reader's backpressure is respected. Cancellable,
|
|
67
|
-
* async-iterable wherever the platform supports it (Node, Deno), and pipeable
|
|
68
|
-
* through any {@link TransformStream} / {@link WritableStream}.
|
|
69
|
-
*
|
|
70
|
-
* @example
|
|
71
|
-
* ```ts
|
|
72
|
-
* // universal - works in every ReadableStream-supporting environment
|
|
73
|
-
* const reader = markdown.stream().getReader()
|
|
74
|
-
* for (let result = await reader.read(); !result.done; result = await reader.read()) {
|
|
75
|
-
* console.log(result.value) // one BlockNode
|
|
76
|
-
* }
|
|
77
|
-
*
|
|
78
|
-
* // Node / Deno / Firefox support async iteration of ReadableStream natively;
|
|
79
|
-
* // other environments should use the reader loop above instead.
|
|
80
|
-
* for await (const block of markdown.stream()) {
|
|
81
|
-
* console.log(block)
|
|
82
|
-
* }
|
|
83
|
-
* ```
|
|
84
|
-
*/
|
|
85
|
-
stream(): ReadableStream<BlockNode>;
|
|
86
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The URL schemes `renderHTML` permits on a link `href` - anything else (notably
|
|
3
|
-
* `javascript:`, `data:`, `vbscript:`, `file:`) is dropped to an empty `href` so a
|
|
4
|
-
* hostile link can never execute. Frozen, lower-case; a relative / anchor /
|
|
5
|
-
* scheme-less `href` (no `scheme:` prefix) is always allowed.
|
|
6
|
-
*/
|
|
7
|
-
export declare const SAFE_URL_SCHEMES: ReadonlySet<string>;
|
|
8
|
-
/**
|
|
9
|
-
* The maximum recursion depth the parse pipeline (`parseDocument` and its
|
|
10
|
-
* `parsers.ts` helpers) and the `helpers.ts` traversal / render functions
|
|
11
|
-
* (`renderHTML`, `renderMarkdown`, `walkNodes`, `foldNode`) honor before degrading to
|
|
12
|
-
* literal text - bounds blockquote nesting, inline nesting (emphasis / links), and
|
|
13
|
-
* traversal/render recursion so pathological or hostile input (deeply nested
|
|
14
|
-
* blockquotes, runaway emphasis) cannot exhaust the call stack. Past this depth the
|
|
15
|
-
* parser treats the remaining content as literal text instead of recursing further.
|
|
16
|
-
*/
|
|
17
|
-
export declare const MAX_DEPTH = 64;
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { ContractInterface } from '@orkestrel/contract';
|
|
2
|
-
import { CodeBlockNode, CodeSpanNode, MarkdownDocument, MarkdownInterface, TextNode, ThematicBreakNode } from './types.js';
|
|
3
|
-
/**
|
|
4
|
-
* Create a stateful markdown handle from a markdown string or an already-parsed
|
|
5
|
-
* {@link MarkdownDocument} - a typed AST plus the query, rewrite, and fold operations
|
|
6
|
-
* {@link MarkdownInterface} exposes.
|
|
7
|
-
*
|
|
8
|
-
* @remarks
|
|
9
|
-
* Given a `string`, runs a block phase (headings / paragraphs / lists / GFM tables /
|
|
10
|
-
* fenced code / blockquotes / thematic breaks) then an inline phase (emphasis /
|
|
11
|
-
* inline code / links) to build a render-agnostic {@link MarkdownDocument}. Given a
|
|
12
|
-
* {@link MarkdownDocument}, adopts it AS-IS without re-validation - gate an untrusted
|
|
13
|
-
* value with `isMarkdownDocument` first. Pure + total parse (malformed markdown
|
|
14
|
-
* degrades to text, never throws) and zero-dependency - a hand-written scanner, no
|
|
15
|
-
* regex-only structural parse, linear-time (no ReDoS).
|
|
16
|
-
*
|
|
17
|
-
* @param input - A markdown string to parse, or an already-parsed {@link MarkdownDocument}
|
|
18
|
-
* @returns A working {@link MarkdownInterface}
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```ts
|
|
22
|
-
* import { createMarkdown } from '@src/core'
|
|
23
|
-
*
|
|
24
|
-
* const markdown = createMarkdown('# Hi\n\nRead the [guide](./guide.md).')
|
|
25
|
-
* markdown.document.children[0] // { element: 'heading', ... }
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
export declare function createMarkdown(input: string | MarkdownDocument): MarkdownInterface;
|
|
29
|
-
/**
|
|
30
|
-
* Compile the {@link textShape} into a {@link ContractInterface} for
|
|
31
|
-
* {@link TextNode} - a guard, coercing parser, JSON Schema, and seeded
|
|
32
|
-
* generator from one shape declaration (AGENTS §14).
|
|
33
|
-
*
|
|
34
|
-
* @returns A `TextNode` contract bundling `schema` / `is` / `parse` / `generate`
|
|
35
|
-
*
|
|
36
|
-
* @example
|
|
37
|
-
* ```ts
|
|
38
|
-
* import { createTextContract } from '@src/core'
|
|
39
|
-
*
|
|
40
|
-
* const text = createTextContract()
|
|
41
|
-
* text.is({ element: 'text', value: 'hi' }) // true
|
|
42
|
-
* ```
|
|
43
|
-
*/
|
|
44
|
-
export declare function createTextContract(): ContractInterface<TextNode>;
|
|
45
|
-
/**
|
|
46
|
-
* Compile the {@link codeSpanShape} into a {@link ContractInterface} for
|
|
47
|
-
* {@link CodeSpanNode} - a guard, coercing parser, JSON Schema, and seeded
|
|
48
|
-
* generator from one shape declaration (AGENTS §14).
|
|
49
|
-
*
|
|
50
|
-
* @returns A `CodeSpanNode` contract bundling `schema` / `is` / `parse` / `generate`
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* ```ts
|
|
54
|
-
* import { createCodeSpanContract } from '@src/core'
|
|
55
|
-
*
|
|
56
|
-
* const codeSpan = createCodeSpanContract()
|
|
57
|
-
* codeSpan.is({ element: 'codeSpan', value: 'const x = 1' }) // true
|
|
58
|
-
* ```
|
|
59
|
-
*/
|
|
60
|
-
export declare function createCodeSpanContract(): ContractInterface<CodeSpanNode>;
|
|
61
|
-
/**
|
|
62
|
-
* Compile the {@link codeBlockShape} into a {@link ContractInterface} for
|
|
63
|
-
* {@link CodeBlockNode} - a guard, coercing parser, JSON Schema, and seeded
|
|
64
|
-
* generator from one shape declaration (AGENTS §14).
|
|
65
|
-
*
|
|
66
|
-
* @returns A `CodeBlockNode` contract bundling `schema` / `is` / `parse` / `generate`
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* ```ts
|
|
70
|
-
* import { createCodeBlockContract } from '@src/core'
|
|
71
|
-
*
|
|
72
|
-
* const codeBlock = createCodeBlockContract()
|
|
73
|
-
* codeBlock.is({ element: 'codeBlock', code: 'x' }) // true
|
|
74
|
-
* ```
|
|
75
|
-
*/
|
|
76
|
-
export declare function createCodeBlockContract(): ContractInterface<CodeBlockNode>;
|
|
77
|
-
/**
|
|
78
|
-
* Compile the {@link thematicBreakShape} into a {@link ContractInterface} for
|
|
79
|
-
* {@link ThematicBreakNode} - a guard, coercing parser, JSON Schema, and
|
|
80
|
-
* seeded generator from one shape declaration (AGENTS §14).
|
|
81
|
-
*
|
|
82
|
-
* @returns A `ThematicBreakNode` contract bundling `schema` / `is` / `parse` / `generate`
|
|
83
|
-
*
|
|
84
|
-
* @example
|
|
85
|
-
* ```ts
|
|
86
|
-
* import { createThematicBreakContract } from '@src/core'
|
|
87
|
-
*
|
|
88
|
-
* const thematicBreak = createThematicBreakContract()
|
|
89
|
-
* thematicBreak.is({ element: 'thematicBreak' }) // true
|
|
90
|
-
* ```
|
|
91
|
-
*/
|
|
92
|
-
export declare function createThematicBreakContract(): ContractInterface<ThematicBreakNode>;
|