@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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@orkestrel/markdown",
3
- "version": "0.0.5",
4
- "description": "Markdown parser toolkit — a fast, zero-surprise CommonMark-style parser with GFM tables, a typed AST, contract-backed node guards, and safe HTML rendering. Part of the @orkestrel line.",
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 \"try{require('node:fs').rmSync('dist',{recursive:true,force:true})}catch{}\"",
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
- "tmp:txt": "node -e \"const fs=require('node:fs'),p=require('node:path');function walk(d){for(const e of fs.readdirSync(d,{withFileTypes:true})){const f=p.join(d,e.name);if(e.isDirectory()){walk(f)}else if(!e.name.endsWith('.md')&&!e.name.endsWith('.txt')){const t=f+'.txt';if(!fs.existsSync(t)){fs.renameSync(f,t)}else{console.warn('Skipping '+f+' — target exists: '+t)}}}}try{walk('tmp')}catch(e){if(e.code!=='ENOENT')throw e}\"",
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.5"
69
+ "@orkestrel/contract": "^0.0.9",
70
+ "@orkestrel/html": "^0.0.1"
69
71
  },
70
72
  "devDependencies": {
71
- "@orkestrel/guide": "^0.0.4",
72
- "@types/node": "^26.1.1",
73
- "oxfmt": "^0.59.0",
74
- "oxlint": "^1.74.0",
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.1.5",
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>;