cms-renderer 0.6.13 → 0.8.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 +31 -0
- package/dist/lib/ai-preview.d.ts +62 -0
- package/dist/lib/ai-preview.js +114 -0
- package/dist/lib/ai-preview.js.map +1 -0
- package/dist/lib/block-renderer.js +87 -49
- package/dist/lib/block-renderer.js.map +1 -1
- package/dist/lib/custom-schemas.js +126 -42
- package/dist/lib/custom-schemas.js.map +1 -1
- package/dist/lib/docs-markdown.js +49 -1
- package/dist/lib/docs-markdown.js.map +1 -1
- package/dist/lib/markdown-utils.js +46 -1
- package/dist/lib/markdown-utils.js.map +1 -1
- package/dist/lib/parametric-route.js +90 -52
- package/dist/lib/parametric-route.js.map +1 -1
- package/dist/lib/renderer.js +90 -52
- package/dist/lib/renderer.js.map +1 -1
- package/dist/lib/types.d.ts +2 -0
- package/dist/lib/types.js.map +1 -1
- package/package.json +8 -4
|
@@ -147,6 +147,54 @@ async function initMarkdown() {
|
|
|
147
147
|
// lib/docs-markdown.tsx
|
|
148
148
|
import { Fragment as Fragment3 } from "react";
|
|
149
149
|
import { bundledLanguages, createHighlighter } from "shiki";
|
|
150
|
+
|
|
151
|
+
// lib/markdown-sanitize.ts
|
|
152
|
+
function stripMarkdownFormattingMarkers(value) {
|
|
153
|
+
return value.replaceAll("`", "").replaceAll("**", "").replaceAll("__", "").replaceAll("~~", "").replaceAll("*", "").replaceAll("_", "");
|
|
154
|
+
}
|
|
155
|
+
function findUnescaped(content, char, from) {
|
|
156
|
+
let index = from;
|
|
157
|
+
while (index < content.length) {
|
|
158
|
+
const found = content.indexOf(char, index);
|
|
159
|
+
if (found === -1) return -1;
|
|
160
|
+
let backslashCount = 0;
|
|
161
|
+
for (let i = found - 1; i >= 0 && content.charAt(i) === "\\"; i -= 1) {
|
|
162
|
+
backslashCount += 1;
|
|
163
|
+
}
|
|
164
|
+
if (backslashCount % 2 === 0) return found;
|
|
165
|
+
index = found + 1;
|
|
166
|
+
}
|
|
167
|
+
return -1;
|
|
168
|
+
}
|
|
169
|
+
function stripMarkdownFormattingFromImageAltText(content) {
|
|
170
|
+
let result = content;
|
|
171
|
+
let index = 0;
|
|
172
|
+
while (index < result.length) {
|
|
173
|
+
const imageStart = result.indexOf("![", index);
|
|
174
|
+
if (imageStart === -1) break;
|
|
175
|
+
const altStart = imageStart + 2;
|
|
176
|
+
const altEnd = findUnescaped(result, "]", altStart);
|
|
177
|
+
if (altEnd === -1) {
|
|
178
|
+
index = altStart;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (result.charAt(altEnd + 1) !== "(") {
|
|
182
|
+
index = altEnd + 1;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const altText = result.slice(altStart, altEnd);
|
|
186
|
+
const plainAltText = stripMarkdownFormattingMarkers(altText);
|
|
187
|
+
if (plainAltText === altText) {
|
|
188
|
+
index = altEnd + 2;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
result = result.slice(0, altStart) + plainAltText + result.slice(altEnd);
|
|
192
|
+
index = altStart + plainAltText.length + 2;
|
|
193
|
+
}
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// lib/docs-markdown.tsx
|
|
150
198
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
151
199
|
var defaultClassName = "cms-docs-markdown";
|
|
152
200
|
var markdownInitPromise;
|
|
@@ -329,7 +377,7 @@ async function DocsMarkdown({
|
|
|
329
377
|
renderImage
|
|
330
378
|
}) {
|
|
331
379
|
await initMarkdown2();
|
|
332
|
-
const ast = mdToJSON2(content);
|
|
380
|
+
const ast = mdToJSON2(stripMarkdownFormattingFromImageAltText(content));
|
|
333
381
|
const imageIndexRef = { current: 0 };
|
|
334
382
|
const rendered = await Promise.all(
|
|
335
383
|
ast.children.map(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../packages/markdown-wasm/src/index.ts","../../../../packages/markdown-wasm/src/components.tsx","../../../../packages/markdown-wasm/src/renderer.tsx","../../../../packages/markdown-wasm/src/types.ts","../../lib/docs-markdown.tsx"],"sourcesContent":["/**\n * @repo/markdown-wasm\n *\n * High-performance markdown rendering using md4w WASM engine\n * with React component mapping support.\n *\n * @example\n * ```tsx\n * import { MarkdownRenderer, createMarkdownRenderer, NodeType } from \"@repo/markdown-wasm\";\n *\n * // Basic usage\n * <MarkdownRenderer content=\"# Hello **world**\" />\n *\n * // With custom components\n * <MarkdownRenderer\n * content={markdown}\n * components={{\n * [NodeType.H1]: ({ children }) => <MyHeading>{children}</MyHeading>,\n * }}\n * />\n *\n * // Factory pattern for reusable renderer\n * const CustomRenderer = createMarkdownRenderer({\n * components: { ... },\n * className: \"prose\",\n * });\n * <CustomRenderer content={markdown} />\n * ```\n */\n\n// Re-export md4w utilities for advanced usage\nexport { init, mdToHtml, mdToJSON, mdToReadableHtml, ParseFlags as Md4wParseFlags } from 'md4w';\n\n// Component exports\nexport { defaultComponents, minimalComponents } from './components';\n// Main renderer exports\nexport {\n createMarkdownRenderer,\n initMarkdown,\n MarkdownRenderer,\n NodeType,\n renderMarkdown,\n} from './renderer';\n// Type exports\nexport type {\n ComponentMap,\n CreateRendererOptions,\n GenericNodeComponent,\n HeadingLevel,\n MarkdownRendererProps,\n MDNode,\n MDTree,\n NodeComponent,\n NodePropsFor,\n NodeRenderProps,\n Options,\n ParseFlags,\n} from './types';\n// Utility exports\nexport { getHeadingLevel, isElementNode, isTextNode } from './types';\n","/**\n * @repo/markdown-wasm - Default Component Mappings\n *\n * Provides semantic HTML components for all md4w node types.\n * Override any of these by passing custom components to MarkdownRenderer.\n */\n\nimport { NodeType } from 'md4w';\nimport type { ReactNode } from 'react';\n\nimport type { ComponentMap, HeadingLevel } from './types';\n\n/**\n * Default heading component.\n * Renders H1-H6 based on level prop.\n */\nfunction Heading({ level, children }: { level: HeadingLevel; children: ReactNode }) {\n const Tag = `h${level}` as const;\n return <Tag>{children}</Tag>;\n}\n\n/**\n * Default components for all md4w node types.\n * These render semantic HTML elements that can be styled with CSS.\n *\n * Props are accessed from a generic props object to satisfy GenericNodeComponent type.\n * Runtime guarantees from md4w ensure the props exist for each node type.\n */\nexport const defaultComponents: ComponentMap = {\n // Block elements\n [NodeType.QUOTE]: (props) => <blockquote>{props.children}</blockquote>,\n [NodeType.UL]: (props) => <ul>{props.children}</ul>,\n [NodeType.OL]: (props) => <ol start={props.start as number | undefined}>{props.children}</ol>,\n [NodeType.LI]: (props) => {\n const isTask = props.isTask as boolean | undefined;\n const done = props.done as boolean | undefined;\n if (isTask) {\n return (\n <li>\n <input type=\"checkbox\" checked={done} disabled readOnly />\n {props.children}\n </li>\n );\n }\n return <li>{props.children}</li>;\n },\n [NodeType.HR]: () => <hr />,\n [NodeType.CODE_BLOCK]: (props: { lang?: string; children: React.ReactNode }) => {\n const language = props.lang?.toLowerCase() || 'plaintext';\n const languageClass = language ? `language-${language}` : undefined;\n return (\n <pre className={languageClass}>\n <code className={languageClass} data-language={language}>\n {props.children}\n </code>\n </pre>\n );\n },\n [NodeType.HTML]: (props) => {\n const html = getHtmlString(props.children);\n if (html && isLineBreakHtml(html)) {\n return <br />;\n }\n // HTML blocks are rendered as-is inside a div\n // Note: This is raw HTML from markdown, use dangerouslySetInnerHTML if needed\n return <div data-markdown-html>{props.children}</div>;\n },\n [NodeType.P]: (props) => <p>{props.children}</p>,\n\n // Table elements\n [NodeType.TABLE]: (props) => <table>{props.children}</table>,\n [NodeType.THEAD]: (props) => <thead>{props.children}</thead>,\n [NodeType.TBODY]: (props) => <tbody>{props.children}</tbody>,\n [NodeType.TR]: (props) => <tr>{props.children}</tr>,\n [NodeType.TH]: (props) => {\n const align = props.align as 'left' | 'center' | 'right' | '' | undefined;\n return <th style={align ? { textAlign: align } : undefined}>{props.children}</th>;\n },\n [NodeType.TD]: (props) => {\n const align = props.align as 'left' | 'center' | 'right' | '' | undefined;\n return <td style={align ? { textAlign: align } : undefined}>{props.children}</td>;\n },\n\n // Headings (H1-H6 share the same component with level prop injected by renderer)\n [NodeType.H1]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 1}>{props.children}</Heading>\n ),\n [NodeType.H2]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 2}>{props.children}</Heading>\n ),\n [NodeType.H3]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 3}>{props.children}</Heading>\n ),\n [NodeType.H4]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 4}>{props.children}</Heading>\n ),\n [NodeType.H5]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 5}>{props.children}</Heading>\n ),\n [NodeType.H6]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 6}>{props.children}</Heading>\n ),\n\n // Inline elements\n [NodeType.EM]: (props) => <em>{props.children}</em>,\n [NodeType.STRONG]: (props) => <strong>{props.children}</strong>,\n [NodeType.A]: (props) => (\n <a href={props.href as string} title={props.title as string | undefined}>\n {props.children}\n </a>\n ),\n [NodeType.IMG]: (props) => (\n // biome-ignore lint/performance/noImgElement: Generic markdown package, not Next.js specific. Users can override with next/image.\n <img\n src={props.src as string}\n alt={props.alt as string}\n title={props.title as string | undefined}\n />\n ),\n [NodeType.CODE_SPAN]: (props) => <code>{props.children}</code>,\n [NodeType.DEL]: (props) => <del>{props.children}</del>,\n\n // Math (LaTeX)\n [NodeType.LATEXMATH]: (props) => <span data-math=\"inline\">{props.children}</span>,\n [NodeType.LATEXMATH_DISPLAY]: (props) => <div data-math=\"display\">{props.children}</div>,\n\n // Wiki links\n [NodeType.WIKILINK]: (props) => {\n const target = props.target as string;\n return (\n <a href={`/wiki/${encodeURIComponent(target)}`} data-wikilink={target}>\n {props.children}\n </a>\n );\n },\n\n // Underline (when UNDERLINE parse flag is enabled)\n [NodeType.U]: (props) => <u>{props.children}</u>,\n};\n\nfunction getHtmlString(children: ReactNode): string | null {\n if (typeof children === 'string') {\n return children;\n }\n if (Array.isArray(children) && children.length === 1 && typeof children[0] === 'string') {\n return children[0];\n }\n return null;\n}\n\nfunction isLineBreakHtml(html: string): boolean {\n const trimmed = html.trim().toLowerCase();\n return trimmed === '<br>' || trimmed === '<br/>' || trimmed === '<br />';\n}\n\n/**\n * Minimal component set - only essential elements.\n * Use this when you want to style everything yourself.\n */\nexport const minimalComponents: ComponentMap = {\n [NodeType.P]: (props) => <p>{props.children}</p>,\n [NodeType.H1]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 1}>{props.children}</Heading>\n ),\n [NodeType.H2]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 2}>{props.children}</Heading>\n ),\n [NodeType.H3]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 3}>{props.children}</Heading>\n ),\n [NodeType.H4]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 4}>{props.children}</Heading>\n ),\n [NodeType.H5]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 5}>{props.children}</Heading>\n ),\n [NodeType.H6]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 6}>{props.children}</Heading>\n ),\n [NodeType.STRONG]: (props) => <strong>{props.children}</strong>,\n [NodeType.EM]: (props) => <em>{props.children}</em>,\n [NodeType.A]: (props) => <a href={props.href as string}>{props.children}</a>,\n [NodeType.CODE_SPAN]: (props) => <code>{props.children}</code>,\n};\n","/**\n * @repo/markdown-wasm - AST to React Renderer\n *\n * Converts md4w JSON AST to React components.\n * Pattern B from research: Direct AST-to-React mapping for maximum performance.\n *\n * IMPORTANT: md4w is a WASM module that requires async initialization.\n * This module uses a singleton pattern with cached promise to ensure\n * init() is called exactly once before any parsing.\n *\n * NOTE: For Vercel/Next.js deployment, ensure `serverExternalPackages: ['md4w']`\n * is set in next.config.ts so md4w can properly resolve its WASM files.\n */\n\nimport { init, mdToJSON, NodeType } from 'md4w';\nimport { Fragment, type ReactNode } from 'react';\n\nimport { defaultComponents } from './components';\nimport type { ComponentMap, CreateRendererOptions, MarkdownRendererProps, MDNode } from './types';\nimport { getHeadingLevel, isTextNode } from './types';\n\n// -----------------------------------------------------------------------------\n// WASM Initialization (Singleton Pattern)\n// -----------------------------------------------------------------------------\n\n/**\n * Cached initialization promise.\n * Ensures init() is called exactly once, even with concurrent calls.\n */\nlet initPromise: Promise<void> | null = null;\n\n/**\n * Ensures md4w WASM is initialized before use.\n * Uses singleton pattern - multiple calls return the same promise.\n *\n * When md4w is listed in `serverExternalPackages`, it will be kept as an\n * external package and can properly resolve its WASM file using import.meta.url.\n *\n * @returns Promise that resolves when WASM is ready\n */\nasync function ensureInitialized(): Promise<void> {\n if (!initPromise) {\n // Use \"small\" variant (28KB gzipped) for faster loading\n initPromise = init('small');\n }\n return initPromise;\n}\n\n// -----------------------------------------------------------------------------\n// Internal Helpers\n// -----------------------------------------------------------------------------\n\n/**\n * Recursively renders an AST node to React elements.\n *\n * @param node - The AST node (MDNode or string)\n * @param components - Component map for rendering\n * @param key - React key for list rendering\n * @returns React element or text\n */\nfunction renderNode(\n node: string | MDNode,\n components: ComponentMap,\n key?: string | number\n): ReactNode {\n // Text nodes render as-is\n if (isTextNode(node)) {\n return node;\n }\n\n const { type, props, children } = node;\n\n // Recursively render children first\n const renderedChildren = children?.map((child, index) => renderNode(child, components, index));\n\n // Get component for this node type\n const Component = components[type as NodeType];\n\n if (Component) {\n // Build props based on node type\n const componentProps = buildComponentProps(type, props);\n\n return (\n <Fragment key={key}>{Component({ ...componentProps, children: renderedChildren })}</Fragment>\n );\n }\n\n // Unmapped node types: warn in development and render children only\n if (process.env.NODE_ENV === 'development') {\n console.warn(\n `[@repo/markdown-wasm] Unmapped node type: ${type} (${NodeType[type] ?? 'unknown'})`\n );\n }\n\n return <Fragment key={key}>{renderedChildren}</Fragment>;\n}\n\n/**\n * Builds component props from AST node props.\n * Handles special cases like heading levels.\n */\nfunction buildComponentProps(\n type: number,\n props?: Record<string, unknown>\n): Record<string, unknown> {\n const result = { ...props };\n\n // Add heading level for H1-H6 nodes\n const headingLevel = getHeadingLevel(type as NodeType);\n if (headingLevel !== undefined) {\n result.level = headingLevel;\n }\n\n return result;\n}\n\n// -----------------------------------------------------------------------------\n// Public API\n// -----------------------------------------------------------------------------\n\n/**\n * Renders markdown content to React elements.\n *\n * This is an async Server Component that initializes the WASM module\n * on first use, then parses and renders markdown content.\n *\n * @example\n * ```tsx\n * import { MarkdownRenderer } from \"@repo/markdown-wasm\";\n *\n * // In a Server Component (async)\n * async function MyComponent() {\n * return <MarkdownRenderer content=\"# Hello **world**\" />;\n * }\n *\n * // Or with await\n * export default async function Page() {\n * return (\n * <article>\n * {await MarkdownRenderer({ content: markdown })}\n * </article>\n * );\n * }\n * ```\n */\nexport async function MarkdownRenderer({\n content,\n components: componentOverrides,\n parseFlags,\n className,\n}: MarkdownRendererProps): Promise<ReactNode> {\n // Ensure WASM is initialized before parsing\n await ensureInitialized();\n\n // Parse markdown to AST\n const ast = mdToJSON(content, { parseFlags });\n\n // Merge default components with overrides\n const components: ComponentMap = {\n ...defaultComponents,\n ...componentOverrides,\n };\n\n // Render AST children\n const rendered = ast.children.map((node, index) => renderNode(node, components, index));\n\n // Wrap in container if className provided, otherwise return fragment\n if (className) {\n return <div className={className}>{rendered}</div>;\n }\n\n return <>{rendered}</>;\n}\n\n/**\n * Creates a pre-configured markdown renderer with default settings.\n *\n * Returns an async function suitable for Server Components.\n *\n * @example\n * ```tsx\n * import { createMarkdownRenderer } from \"@repo/markdown-wasm\";\n * import { MyHeading, MyText, MyLink } from \"./components\";\n *\n * const CustomRenderer = createMarkdownRenderer({\n * components: {\n * [NodeType.H1]: ({ level, children }) => <MyHeading level={level}>{children}</MyHeading>,\n * [NodeType.P]: ({ children }) => <MyText>{children}</MyText>,\n * [NodeType.A]: ({ href, children }) => <MyLink href={href}>{children}</MyLink>,\n * },\n * className: \"prose prose-lg\",\n * });\n *\n * async function Article({ markdown }: { markdown: string }) {\n * return <CustomRenderer content={markdown} />;\n * }\n * ```\n */\nexport function createMarkdownRenderer(options: CreateRendererOptions) {\n const {\n components: defaultOverrides,\n parseFlags: defaultParseFlags,\n className: defaultClassName,\n } = options;\n\n return async function ConfiguredMarkdownRenderer({\n content,\n components: instanceOverrides,\n parseFlags: instanceParseFlags,\n className: instanceClassName,\n }: MarkdownRendererProps): Promise<ReactNode> {\n return MarkdownRenderer({\n content,\n components: { ...defaultOverrides, ...instanceOverrides },\n parseFlags: instanceParseFlags ?? defaultParseFlags,\n className: instanceClassName ?? defaultClassName,\n });\n };\n}\n\n/**\n * Renders markdown to React elements without a wrapper component.\n * Useful for embedding markdown content inline.\n *\n * @example\n * ```tsx\n * import { renderMarkdown } from \"@repo/markdown-wasm\";\n *\n * // In an async Server Component\n * const elements = await renderMarkdown(\"**bold** and *italic*\");\n * ```\n */\nexport async function renderMarkdown(\n content: string,\n options?: Omit<MarkdownRendererProps, 'content'>\n): Promise<ReactNode> {\n return MarkdownRenderer({ content, ...options });\n}\n\n/**\n * Explicitly initialize the md4w WASM module.\n * Useful for pre-warming in middleware or layout components.\n *\n * @example\n * ```tsx\n * // In layout.tsx\n * import { initMarkdown } from \"@repo/markdown-wasm\";\n *\n * export default async function RootLayout({ children }) {\n * // Pre-warm WASM for faster first render\n * await initMarkdown();\n * return <html>{children}</html>;\n * }\n * ```\n */\nexport async function initMarkdown(): Promise<void> {\n await ensureInitialized();\n}\n\n// Re-export for convenience\nexport { NodeType } from 'md4w';\n","/**\n * @repo/markdown-wasm - Type definitions\n *\n * Re-exports md4w types with additional utility types for React rendering.\n */\n\nimport type { MDNode, MDTree, NodeType, Options, ParseFlags } from 'md4w';\nimport type { ReactNode } from 'react';\n\n// Re-export md4w types\nexport type { MDNode, MDTree, NodeType, Options, ParseFlags };\n\n/**\n * Props passed to a custom component for an AST node.\n * Each node type has different props available.\n */\nexport interface NodeRenderProps {\n /** The original AST node */\n node: MDNode;\n /** Rendered children (already converted to React elements) */\n children: ReactNode;\n}\n\n/**\n * A component that renders a specific node type.\n * Receives the node props and pre-rendered children.\n */\nexport type NodeComponent<TProps = Record<string, unknown>> = (\n props: TProps & { children: ReactNode }\n) => ReactNode;\n\n/**\n * Generic component function used internally.\n * Accepts any props and children.\n */\nexport type GenericNodeComponent = (\n props: Record<string, unknown> & { children: ReactNode }\n) => ReactNode;\n\n/**\n * Component overrides map keyed by NodeType.\n * Each entry maps a node type number to a component function.\n * Uses GenericNodeComponent internally for flexibility.\n */\nexport type ComponentMap = Partial<Record<NodeType, GenericNodeComponent>>;\n\n/**\n * Utility type to extract props for a specific node type.\n * This provides type-safe access to node-specific properties.\n */\nexport type NodePropsFor<T extends NodeType> = T extends NodeType.CODE_BLOCK\n ? { lang?: string }\n : T extends NodeType.OL\n ? { start?: number }\n : T extends NodeType.LI\n ? { isTask?: boolean; done?: boolean }\n : T extends NodeType.TH | NodeType.TD\n ? { align?: 'left' | 'center' | 'right' | '' }\n : T extends NodeType.A\n ? { href: string; title?: string }\n : T extends NodeType.IMG\n ? { src: string; alt: string; title?: string }\n : T extends NodeType.WIKILINK\n ? { target: string }\n : T extends\n | NodeType.H1\n | NodeType.H2\n | NodeType.H3\n | NodeType.H4\n | NodeType.H5\n | NodeType.H6\n ? { level: 1 | 2 | 3 | 4 | 5 | 6 }\n : Record<string, unknown>;\n\n/**\n * Props for the MarkdownRenderer component.\n */\nexport interface MarkdownRendererProps {\n /** Markdown content to render */\n content: string;\n /** Optional component overrides */\n components?: ComponentMap;\n /** Optional parse flags for md4w */\n parseFlags?: Options['parseFlags'];\n /** Optional className for the wrapper element */\n className?: string;\n}\n\n/**\n * Factory function options for creating a custom renderer.\n */\nexport interface CreateRendererOptions {\n /** Component overrides */\n components?: ComponentMap;\n /** Default parse flags */\n parseFlags?: Options['parseFlags'];\n /** Default wrapper className */\n className?: string;\n}\n\n/**\n * Heading level type (1-6).\n */\nexport type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;\n\n/**\n * Maps NodeType.H1-H6 to numeric heading level.\n */\nexport function getHeadingLevel(type: NodeType): HeadingLevel | undefined {\n const levelMap: Record<number, HeadingLevel> = {\n 21: 1,\n 22: 2,\n 23: 3,\n 24: 4,\n 25: 5,\n 26: 6,\n };\n return levelMap[type];\n}\n\n/**\n * Type guard to check if a node is a text node (string).\n */\nexport function isTextNode(node: string | MDNode): node is string {\n return typeof node === 'string';\n}\n\n/**\n * Type guard to check if a node is an element node (MDNode).\n */\nexport function isElementNode(node: string | MDNode): node is MDNode {\n return typeof node !== 'string' && typeof node === 'object' && 'type' in node;\n}\n","import {\n type ComponentMap,\n defaultComponents,\n getHeadingLevel,\n initMarkdown as initMarkdownWasm,\n type MDNode,\n mdToJSON,\n NodeType,\n} from '@repo/markdown-wasm';\nimport { Fragment, type ReactNode } from 'react';\nimport { type BundledLanguage, bundledLanguages, createHighlighter, type Highlighter } from 'shiki';\n\nexport interface DocsMarkdownProps {\n content: string;\n className?: string;\n renderImage?: (props: {\n src: string;\n alt: string;\n title?: string;\n loading?: 'eager' | 'lazy';\n }) => ReactNode;\n}\n\nconst defaultClassName = 'cms-docs-markdown';\nlet markdownInitPromise: Promise<void> | undefined;\n\n// Pre-load common languages for fast cold-start\n// Less common languages are lazy-loaded on demand\nconst PRELOADED_LANGS: BundledLanguage[] = [\n // Web\n 'typescript',\n 'javascript',\n 'tsx',\n 'jsx',\n 'html',\n 'css',\n 'scss',\n // Systems\n 'c',\n 'cpp',\n 'rust',\n 'go',\n // Enterprise\n 'java',\n 'csharp',\n 'kotlin',\n 'swift',\n // Scripting\n 'python',\n 'ruby',\n 'php',\n 'bash',\n 'shellscript',\n 'powershell',\n // Data\n 'json',\n 'yaml',\n 'toml',\n 'xml',\n 'sql',\n 'graphql',\n // Other\n 'markdown',\n 'dockerfile',\n];\n\n// Cache the highlighter instance to avoid re-loading languages/themes\nlet highlighterPromise: Promise<Highlighter> | undefined;\n\n// Track which languages have been loaded\nconst loadedLanguages = new Set<string>(PRELOADED_LANGS);\n\nasync function getHighlighter(): Promise<Highlighter> {\n highlighterPromise ??= createHighlighter({\n themes: ['github-dark'],\n langs: PRELOADED_LANGS,\n });\n return highlighterPromise;\n}\n\n/**\n * Ensures a language is loaded, lazy-loading if needed.\n * Returns the resolved language name, or null if not a valid language.\n */\nasync function ensureLanguageLoaded(\n highlighter: Highlighter,\n lang: string\n): Promise<BundledLanguage | null> {\n // Already loaded\n if (loadedLanguages.has(lang)) {\n return lang as BundledLanguage;\n }\n\n // Check if it's a valid bundled language\n if (lang in bundledLanguages) {\n try {\n await highlighter.loadLanguage(lang as BundledLanguage);\n loadedLanguages.add(lang);\n return lang as BundledLanguage;\n } catch {\n // Loading failed, will render as plain code\n return null;\n }\n }\n\n // Unknown language, will render as plain code\n return null;\n}\n\n// Pre-warm WASM and Shiki at module load to avoid cold start penalty\n// This runs once when the module is first imported (server startup)\nif (typeof window === 'undefined') {\n // Server-side only: trigger initialization immediately\n markdownInitPromise = initMarkdownWasm();\n highlighterPromise = createHighlighter({\n themes: ['github-dark'],\n langs: PRELOADED_LANGS,\n });\n // Log pre-warming for debugging\n Promise.all([markdownInitPromise, highlighterPromise])\n .then(() =>\n console.log(`[docs-markdown] WASM and Shiki pre-warmed (${PRELOADED_LANGS.length} languages)`)\n )\n .catch((err) => console.error('[docs-markdown] Pre-warm failed:', err));\n}\n\nexport function markdownStartsWithHeading(markdown: string): boolean {\n return /^\\s*#\\s+/.test(markdown);\n}\n\nexport async function initMarkdown(): Promise<void> {\n markdownInitPromise ??= initMarkdownWasm();\n await markdownInitPromise;\n}\n\nfunction isTextNode(node: string | MDNode): node is string {\n return typeof node === 'string';\n}\n\nfunction buildComponentProps(\n type: number,\n props?: Record<string, unknown>\n): Record<string, unknown> {\n const result = { ...props };\n const headingLevel = getHeadingLevel(type as NodeType);\n\n if (headingLevel !== undefined) {\n result.level = headingLevel;\n }\n\n return result;\n}\n\nfunction normalizeLanguage(lang: unknown): string {\n if (typeof lang !== 'string') {\n return '';\n }\n\n const normalized = lang.trim().toLowerCase();\n return normalized;\n}\n\nfunction decodeHtmlEntities(value: string): string {\n return value\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('"', '\"')\n .replaceAll(''', \"'\")\n .replaceAll('&', '&');\n}\n\nasync function renderHighlightedCode(code: string, lang: unknown): Promise<ReactNode> {\n const language = normalizeLanguage(lang);\n const displayLanguage = language || 'text';\n\n try {\n const highlighter = await getHighlighter();\n // Ensure language is loaded (lazy-load if needed)\n const resolvedLang = await ensureLanguageLoaded(highlighter, language);\n\n // If language not supported, render plain code\n if (!resolvedLang) {\n return (\n <pre className={`language-${displayLanguage}`}>\n <code className={`language-${displayLanguage}`}>{code}</code>\n </pre>\n );\n }\n\n const result = highlighter.codeToTokens(code, {\n lang: resolvedLang,\n theme: 'github-dark',\n });\n\n return (\n <pre\n className={`shiki language-${displayLanguage}`}\n style={{ backgroundColor: result.bg, color: result.fg }}\n >\n <code className={`shiki_code language-${displayLanguage}`} data-language={displayLanguage}>\n {(() => {\n const lineOccurrences = new Map<string, number>();\n\n return result.tokens.map((line, lineIndex) => {\n const isLastLine = lineIndex === result.tokens.length - 1;\n const lineSignature = line\n .map((token) => `${token.offset}:${token.color ?? ''}:${token.content}`)\n .join('|');\n const lineOccurrence = lineOccurrences.get(lineSignature) ?? 0;\n\n lineOccurrences.set(lineSignature, lineOccurrence + 1);\n\n let tokenCursor = 0;\n\n return (\n <Fragment key={`${lineSignature}#${lineOccurrence}`}>\n {line.map((token) => {\n const tokenKey = `${token.offset}:${token.color ?? ''}:${tokenCursor}:${token.content}`;\n tokenCursor += token.content.length;\n\n return (\n <span key={tokenKey} style={{ color: token.color }}>\n {token.content}\n </span>\n );\n })}\n {isLastLine ? null : '\\n'}\n </Fragment>\n );\n });\n })()}\n </code>\n </pre>\n );\n } catch {\n return (\n <pre className={`language-${displayLanguage}`}>\n <code className={`language-${displayLanguage}`}>{code}</code>\n </pre>\n );\n }\n}\n\nasync function renderNode(\n node: string | MDNode,\n components: ComponentMap,\n renderImage: DocsMarkdownProps['renderImage'],\n imageIndexRef: { current: number },\n key?: string | number\n): Promise<ReactNode> {\n if (isTextNode(node)) {\n return decodeHtmlEntities(node);\n }\n\n const { type, props, children } = node;\n\n if (type === NodeType.IMG && renderImage) {\n const imageIndex = imageIndexRef.current;\n imageIndexRef.current += 1;\n\n return (\n <Fragment key={key}>\n {renderImage({\n src: typeof props?.src === 'string' ? props.src : '',\n alt: typeof props?.alt === 'string' ? props.alt : '',\n title: typeof props?.title === 'string' ? props.title : undefined,\n loading: imageIndex === 0 ? 'eager' : 'lazy',\n })}\n </Fragment>\n );\n }\n\n if (type === NodeType.CODE_BLOCK) {\n const rawCode = children?.filter(isTextNode).join('') ?? '';\n const code = decodeHtmlEntities(rawCode);\n return <Fragment key={key}>{await renderHighlightedCode(code, props?.lang)}</Fragment>;\n }\n\n const renderedChildren = children\n ? await Promise.all(\n children.map((child, index) =>\n renderNode(child, components, renderImage, imageIndexRef, index)\n )\n )\n : undefined;\n\n const Component = components[type as NodeType];\n\n if (Component) {\n const componentProps = buildComponentProps(type, props);\n return (\n <Fragment key={key}>{Component({ ...componentProps, children: renderedChildren })}</Fragment>\n );\n }\n\n return <Fragment key={key}>{renderedChildren}</Fragment>;\n}\n\nexport async function DocsMarkdown({\n content,\n className = defaultClassName,\n renderImage,\n}: DocsMarkdownProps) {\n await initMarkdown();\n\n const ast = mdToJSON(content);\n const imageIndexRef = { current: 0 };\n const rendered = await Promise.all(\n ast.children.map((node, index) =>\n renderNode(node, defaultComponents, renderImage, imageIndexRef, index)\n )\n );\n\n const resolvedClassName =\n className === defaultClassName ? defaultClassName : `${defaultClassName} ${className}`.trim();\n\n return <div className={resolvedClassName}>{rendered}</div>;\n}\n"],"mappings":"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","names":["init","mdToJSON","NodeType","NodeType","Fragment","jsx","Fragment","jsx","jsxs","initMarkdown","isTextNode","NodeType","mdToJSON"]}
|
|
1
|
+
{"version":3,"sources":["../../../../packages/markdown-wasm/src/index.ts","../../../../packages/markdown-wasm/src/components.tsx","../../../../packages/markdown-wasm/src/renderer.tsx","../../../../packages/markdown-wasm/src/types.ts","../../lib/docs-markdown.tsx","../../lib/markdown-sanitize.ts"],"sourcesContent":["/**\n * @repo/markdown-wasm\n *\n * High-performance markdown rendering using md4w WASM engine\n * with React component mapping support.\n *\n * @example\n * ```tsx\n * import { MarkdownRenderer, createMarkdownRenderer, NodeType } from \"@repo/markdown-wasm\";\n *\n * // Basic usage\n * <MarkdownRenderer content=\"# Hello **world**\" />\n *\n * // With custom components\n * <MarkdownRenderer\n * content={markdown}\n * components={{\n * [NodeType.H1]: ({ children }) => <MyHeading>{children}</MyHeading>,\n * }}\n * />\n *\n * // Factory pattern for reusable renderer\n * const CustomRenderer = createMarkdownRenderer({\n * components: { ... },\n * className: \"prose\",\n * });\n * <CustomRenderer content={markdown} />\n * ```\n */\n\n// Re-export md4w utilities for advanced usage\nexport { init, mdToHtml, mdToJSON, mdToReadableHtml, ParseFlags as Md4wParseFlags } from 'md4w';\n\n// Component exports\nexport { defaultComponents, minimalComponents } from './components';\n// Main renderer exports\nexport {\n createMarkdownRenderer,\n initMarkdown,\n MarkdownRenderer,\n NodeType,\n renderMarkdown,\n} from './renderer';\n// Type exports\nexport type {\n ComponentMap,\n CreateRendererOptions,\n GenericNodeComponent,\n HeadingLevel,\n MarkdownRendererProps,\n MDNode,\n MDTree,\n NodeComponent,\n NodePropsFor,\n NodeRenderProps,\n Options,\n ParseFlags,\n} from './types';\n// Utility exports\nexport { getHeadingLevel, isElementNode, isTextNode } from './types';\n","/**\n * @repo/markdown-wasm - Default Component Mappings\n *\n * Provides semantic HTML components for all md4w node types.\n * Override any of these by passing custom components to MarkdownRenderer.\n */\n\nimport { NodeType } from 'md4w';\nimport type { ReactNode } from 'react';\n\nimport type { ComponentMap, HeadingLevel } from './types';\n\n/**\n * Default heading component.\n * Renders H1-H6 based on level prop.\n */\nfunction Heading({ level, children }: { level: HeadingLevel; children: ReactNode }) {\n const Tag = `h${level}` as const;\n return <Tag>{children}</Tag>;\n}\n\n/**\n * Default components for all md4w node types.\n * These render semantic HTML elements that can be styled with CSS.\n *\n * Props are accessed from a generic props object to satisfy GenericNodeComponent type.\n * Runtime guarantees from md4w ensure the props exist for each node type.\n */\nexport const defaultComponents: ComponentMap = {\n // Block elements\n [NodeType.QUOTE]: (props) => <blockquote>{props.children}</blockquote>,\n [NodeType.UL]: (props) => <ul>{props.children}</ul>,\n [NodeType.OL]: (props) => <ol start={props.start as number | undefined}>{props.children}</ol>,\n [NodeType.LI]: (props) => {\n const isTask = props.isTask as boolean | undefined;\n const done = props.done as boolean | undefined;\n if (isTask) {\n return (\n <li>\n <input type=\"checkbox\" checked={done} disabled readOnly />\n {props.children}\n </li>\n );\n }\n return <li>{props.children}</li>;\n },\n [NodeType.HR]: () => <hr />,\n [NodeType.CODE_BLOCK]: (props: { lang?: string; children: React.ReactNode }) => {\n const language = props.lang?.toLowerCase() || 'plaintext';\n const languageClass = language ? `language-${language}` : undefined;\n return (\n <pre className={languageClass}>\n <code className={languageClass} data-language={language}>\n {props.children}\n </code>\n </pre>\n );\n },\n [NodeType.HTML]: (props) => {\n const html = getHtmlString(props.children);\n if (html && isLineBreakHtml(html)) {\n return <br />;\n }\n // HTML blocks are rendered as-is inside a div\n // Note: This is raw HTML from markdown, use dangerouslySetInnerHTML if needed\n return <div data-markdown-html>{props.children}</div>;\n },\n [NodeType.P]: (props) => <p>{props.children}</p>,\n\n // Table elements\n [NodeType.TABLE]: (props) => <table>{props.children}</table>,\n [NodeType.THEAD]: (props) => <thead>{props.children}</thead>,\n [NodeType.TBODY]: (props) => <tbody>{props.children}</tbody>,\n [NodeType.TR]: (props) => <tr>{props.children}</tr>,\n [NodeType.TH]: (props) => {\n const align = props.align as 'left' | 'center' | 'right' | '' | undefined;\n return <th style={align ? { textAlign: align } : undefined}>{props.children}</th>;\n },\n [NodeType.TD]: (props) => {\n const align = props.align as 'left' | 'center' | 'right' | '' | undefined;\n return <td style={align ? { textAlign: align } : undefined}>{props.children}</td>;\n },\n\n // Headings (H1-H6 share the same component with level prop injected by renderer)\n [NodeType.H1]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 1}>{props.children}</Heading>\n ),\n [NodeType.H2]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 2}>{props.children}</Heading>\n ),\n [NodeType.H3]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 3}>{props.children}</Heading>\n ),\n [NodeType.H4]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 4}>{props.children}</Heading>\n ),\n [NodeType.H5]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 5}>{props.children}</Heading>\n ),\n [NodeType.H6]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 6}>{props.children}</Heading>\n ),\n\n // Inline elements\n [NodeType.EM]: (props) => <em>{props.children}</em>,\n [NodeType.STRONG]: (props) => <strong>{props.children}</strong>,\n [NodeType.A]: (props) => (\n <a href={props.href as string} title={props.title as string | undefined}>\n {props.children}\n </a>\n ),\n [NodeType.IMG]: (props) => (\n // biome-ignore lint/performance/noImgElement: Generic markdown package, not Next.js specific. Users can override with next/image.\n <img\n src={props.src as string}\n alt={props.alt as string}\n title={props.title as string | undefined}\n />\n ),\n [NodeType.CODE_SPAN]: (props) => <code>{props.children}</code>,\n [NodeType.DEL]: (props) => <del>{props.children}</del>,\n\n // Math (LaTeX)\n [NodeType.LATEXMATH]: (props) => <span data-math=\"inline\">{props.children}</span>,\n [NodeType.LATEXMATH_DISPLAY]: (props) => <div data-math=\"display\">{props.children}</div>,\n\n // Wiki links\n [NodeType.WIKILINK]: (props) => {\n const target = props.target as string;\n return (\n <a href={`/wiki/${encodeURIComponent(target)}`} data-wikilink={target}>\n {props.children}\n </a>\n );\n },\n\n // Underline (when UNDERLINE parse flag is enabled)\n [NodeType.U]: (props) => <u>{props.children}</u>,\n};\n\nfunction getHtmlString(children: ReactNode): string | null {\n if (typeof children === 'string') {\n return children;\n }\n if (Array.isArray(children) && children.length === 1 && typeof children[0] === 'string') {\n return children[0];\n }\n return null;\n}\n\nfunction isLineBreakHtml(html: string): boolean {\n const trimmed = html.trim().toLowerCase();\n return trimmed === '<br>' || trimmed === '<br/>' || trimmed === '<br />';\n}\n\n/**\n * Minimal component set - only essential elements.\n * Use this when you want to style everything yourself.\n */\nexport const minimalComponents: ComponentMap = {\n [NodeType.P]: (props) => <p>{props.children}</p>,\n [NodeType.H1]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 1}>{props.children}</Heading>\n ),\n [NodeType.H2]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 2}>{props.children}</Heading>\n ),\n [NodeType.H3]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 3}>{props.children}</Heading>\n ),\n [NodeType.H4]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 4}>{props.children}</Heading>\n ),\n [NodeType.H5]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 5}>{props.children}</Heading>\n ),\n [NodeType.H6]: (props) => (\n <Heading level={(props.level as HeadingLevel) ?? 6}>{props.children}</Heading>\n ),\n [NodeType.STRONG]: (props) => <strong>{props.children}</strong>,\n [NodeType.EM]: (props) => <em>{props.children}</em>,\n [NodeType.A]: (props) => <a href={props.href as string}>{props.children}</a>,\n [NodeType.CODE_SPAN]: (props) => <code>{props.children}</code>,\n};\n","/**\n * @repo/markdown-wasm - AST to React Renderer\n *\n * Converts md4w JSON AST to React components.\n * Pattern B from research: Direct AST-to-React mapping for maximum performance.\n *\n * IMPORTANT: md4w is a WASM module that requires async initialization.\n * This module uses a singleton pattern with cached promise to ensure\n * init() is called exactly once before any parsing.\n *\n * NOTE: For Vercel/Next.js deployment, ensure `serverExternalPackages: ['md4w']`\n * is set in next.config.ts so md4w can properly resolve its WASM files.\n */\n\nimport { init, mdToJSON, NodeType } from 'md4w';\nimport { Fragment, type ReactNode } from 'react';\n\nimport { defaultComponents } from './components';\nimport type { ComponentMap, CreateRendererOptions, MarkdownRendererProps, MDNode } from './types';\nimport { getHeadingLevel, isTextNode } from './types';\n\n// -----------------------------------------------------------------------------\n// WASM Initialization (Singleton Pattern)\n// -----------------------------------------------------------------------------\n\n/**\n * Cached initialization promise.\n * Ensures init() is called exactly once, even with concurrent calls.\n */\nlet initPromise: Promise<void> | null = null;\n\n/**\n * Ensures md4w WASM is initialized before use.\n * Uses singleton pattern - multiple calls return the same promise.\n *\n * When md4w is listed in `serverExternalPackages`, it will be kept as an\n * external package and can properly resolve its WASM file using import.meta.url.\n *\n * @returns Promise that resolves when WASM is ready\n */\nasync function ensureInitialized(): Promise<void> {\n if (!initPromise) {\n // Use \"small\" variant (28KB gzipped) for faster loading\n initPromise = init('small');\n }\n return initPromise;\n}\n\n// -----------------------------------------------------------------------------\n// Internal Helpers\n// -----------------------------------------------------------------------------\n\n/**\n * Recursively renders an AST node to React elements.\n *\n * @param node - The AST node (MDNode or string)\n * @param components - Component map for rendering\n * @param key - React key for list rendering\n * @returns React element or text\n */\nfunction renderNode(\n node: string | MDNode,\n components: ComponentMap,\n key?: string | number\n): ReactNode {\n // Text nodes render as-is\n if (isTextNode(node)) {\n return node;\n }\n\n const { type, props, children } = node;\n\n // Recursively render children first\n const renderedChildren = children?.map((child, index) => renderNode(child, components, index));\n\n // Get component for this node type\n const Component = components[type as NodeType];\n\n if (Component) {\n // Build props based on node type\n const componentProps = buildComponentProps(type, props);\n\n return (\n <Fragment key={key}>{Component({ ...componentProps, children: renderedChildren })}</Fragment>\n );\n }\n\n // Unmapped node types: warn in development and render children only\n if (process.env.NODE_ENV === 'development') {\n console.warn(\n `[@repo/markdown-wasm] Unmapped node type: ${type} (${NodeType[type] ?? 'unknown'})`\n );\n }\n\n return <Fragment key={key}>{renderedChildren}</Fragment>;\n}\n\n/**\n * Builds component props from AST node props.\n * Handles special cases like heading levels.\n */\nfunction buildComponentProps(\n type: number,\n props?: Record<string, unknown>\n): Record<string, unknown> {\n const result = { ...props };\n\n // Add heading level for H1-H6 nodes\n const headingLevel = getHeadingLevel(type as NodeType);\n if (headingLevel !== undefined) {\n result.level = headingLevel;\n }\n\n return result;\n}\n\n// -----------------------------------------------------------------------------\n// Public API\n// -----------------------------------------------------------------------------\n\n/**\n * Renders markdown content to React elements.\n *\n * This is an async Server Component that initializes the WASM module\n * on first use, then parses and renders markdown content.\n *\n * @example\n * ```tsx\n * import { MarkdownRenderer } from \"@repo/markdown-wasm\";\n *\n * // In a Server Component (async)\n * async function MyComponent() {\n * return <MarkdownRenderer content=\"# Hello **world**\" />;\n * }\n *\n * // Or with await\n * export default async function Page() {\n * return (\n * <article>\n * {await MarkdownRenderer({ content: markdown })}\n * </article>\n * );\n * }\n * ```\n */\nexport async function MarkdownRenderer({\n content,\n components: componentOverrides,\n parseFlags,\n className,\n}: MarkdownRendererProps): Promise<ReactNode> {\n // Ensure WASM is initialized before parsing\n await ensureInitialized();\n\n // Parse markdown to AST\n const ast = mdToJSON(content, { parseFlags });\n\n // Merge default components with overrides\n const components: ComponentMap = {\n ...defaultComponents,\n ...componentOverrides,\n };\n\n // Render AST children\n const rendered = ast.children.map((node, index) => renderNode(node, components, index));\n\n // Wrap in container if className provided, otherwise return fragment\n if (className) {\n return <div className={className}>{rendered}</div>;\n }\n\n return <>{rendered}</>;\n}\n\n/**\n * Creates a pre-configured markdown renderer with default settings.\n *\n * Returns an async function suitable for Server Components.\n *\n * @example\n * ```tsx\n * import { createMarkdownRenderer } from \"@repo/markdown-wasm\";\n * import { MyHeading, MyText, MyLink } from \"./components\";\n *\n * const CustomRenderer = createMarkdownRenderer({\n * components: {\n * [NodeType.H1]: ({ level, children }) => <MyHeading level={level}>{children}</MyHeading>,\n * [NodeType.P]: ({ children }) => <MyText>{children}</MyText>,\n * [NodeType.A]: ({ href, children }) => <MyLink href={href}>{children}</MyLink>,\n * },\n * className: \"prose prose-lg\",\n * });\n *\n * async function Article({ markdown }: { markdown: string }) {\n * return <CustomRenderer content={markdown} />;\n * }\n * ```\n */\nexport function createMarkdownRenderer(options: CreateRendererOptions) {\n const {\n components: defaultOverrides,\n parseFlags: defaultParseFlags,\n className: defaultClassName,\n } = options;\n\n return async function ConfiguredMarkdownRenderer({\n content,\n components: instanceOverrides,\n parseFlags: instanceParseFlags,\n className: instanceClassName,\n }: MarkdownRendererProps): Promise<ReactNode> {\n return MarkdownRenderer({\n content,\n components: { ...defaultOverrides, ...instanceOverrides },\n parseFlags: instanceParseFlags ?? defaultParseFlags,\n className: instanceClassName ?? defaultClassName,\n });\n };\n}\n\n/**\n * Renders markdown to React elements without a wrapper component.\n * Useful for embedding markdown content inline.\n *\n * @example\n * ```tsx\n * import { renderMarkdown } from \"@repo/markdown-wasm\";\n *\n * // In an async Server Component\n * const elements = await renderMarkdown(\"**bold** and *italic*\");\n * ```\n */\nexport async function renderMarkdown(\n content: string,\n options?: Omit<MarkdownRendererProps, 'content'>\n): Promise<ReactNode> {\n return MarkdownRenderer({ content, ...options });\n}\n\n/**\n * Explicitly initialize the md4w WASM module.\n * Useful for pre-warming in middleware or layout components.\n *\n * @example\n * ```tsx\n * // In layout.tsx\n * import { initMarkdown } from \"@repo/markdown-wasm\";\n *\n * export default async function RootLayout({ children }) {\n * // Pre-warm WASM for faster first render\n * await initMarkdown();\n * return <html>{children}</html>;\n * }\n * ```\n */\nexport async function initMarkdown(): Promise<void> {\n await ensureInitialized();\n}\n\n// Re-export for convenience\nexport { NodeType } from 'md4w';\n","/**\n * @repo/markdown-wasm - Type definitions\n *\n * Re-exports md4w types with additional utility types for React rendering.\n */\n\nimport type { MDNode, MDTree, NodeType, Options, ParseFlags } from 'md4w';\nimport type { ReactNode } from 'react';\n\n// Re-export md4w types\nexport type { MDNode, MDTree, NodeType, Options, ParseFlags };\n\n/**\n * Props passed to a custom component for an AST node.\n * Each node type has different props available.\n */\nexport interface NodeRenderProps {\n /** The original AST node */\n node: MDNode;\n /** Rendered children (already converted to React elements) */\n children: ReactNode;\n}\n\n/**\n * A component that renders a specific node type.\n * Receives the node props and pre-rendered children.\n */\nexport type NodeComponent<TProps = Record<string, unknown>> = (\n props: TProps & { children: ReactNode }\n) => ReactNode;\n\n/**\n * Generic component function used internally.\n * Accepts any props and children.\n */\nexport type GenericNodeComponent = (\n props: Record<string, unknown> & { children: ReactNode }\n) => ReactNode;\n\n/**\n * Component overrides map keyed by NodeType.\n * Each entry maps a node type number to a component function.\n * Uses GenericNodeComponent internally for flexibility.\n */\nexport type ComponentMap = Partial<Record<NodeType, GenericNodeComponent>>;\n\n/**\n * Utility type to extract props for a specific node type.\n * This provides type-safe access to node-specific properties.\n */\nexport type NodePropsFor<T extends NodeType> = T extends NodeType.CODE_BLOCK\n ? { lang?: string }\n : T extends NodeType.OL\n ? { start?: number }\n : T extends NodeType.LI\n ? { isTask?: boolean; done?: boolean }\n : T extends NodeType.TH | NodeType.TD\n ? { align?: 'left' | 'center' | 'right' | '' }\n : T extends NodeType.A\n ? { href: string; title?: string }\n : T extends NodeType.IMG\n ? { src: string; alt: string; title?: string }\n : T extends NodeType.WIKILINK\n ? { target: string }\n : T extends\n | NodeType.H1\n | NodeType.H2\n | NodeType.H3\n | NodeType.H4\n | NodeType.H5\n | NodeType.H6\n ? { level: 1 | 2 | 3 | 4 | 5 | 6 }\n : Record<string, unknown>;\n\n/**\n * Props for the MarkdownRenderer component.\n */\nexport interface MarkdownRendererProps {\n /** Markdown content to render */\n content: string;\n /** Optional component overrides */\n components?: ComponentMap;\n /** Optional parse flags for md4w */\n parseFlags?: Options['parseFlags'];\n /** Optional className for the wrapper element */\n className?: string;\n}\n\n/**\n * Factory function options for creating a custom renderer.\n */\nexport interface CreateRendererOptions {\n /** Component overrides */\n components?: ComponentMap;\n /** Default parse flags */\n parseFlags?: Options['parseFlags'];\n /** Default wrapper className */\n className?: string;\n}\n\n/**\n * Heading level type (1-6).\n */\nexport type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;\n\n/**\n * Maps NodeType.H1-H6 to numeric heading level.\n */\nexport function getHeadingLevel(type: NodeType): HeadingLevel | undefined {\n const levelMap: Record<number, HeadingLevel> = {\n 21: 1,\n 22: 2,\n 23: 3,\n 24: 4,\n 25: 5,\n 26: 6,\n };\n return levelMap[type];\n}\n\n/**\n * Type guard to check if a node is a text node (string).\n */\nexport function isTextNode(node: string | MDNode): node is string {\n return typeof node === 'string';\n}\n\n/**\n * Type guard to check if a node is an element node (MDNode).\n */\nexport function isElementNode(node: string | MDNode): node is MDNode {\n return typeof node !== 'string' && typeof node === 'object' && 'type' in node;\n}\n","import {\n type ComponentMap,\n defaultComponents,\n getHeadingLevel,\n initMarkdown as initMarkdownWasm,\n type MDNode,\n mdToJSON,\n NodeType,\n} from '@repo/markdown-wasm';\nimport { Fragment, type ReactNode } from 'react';\nimport { type BundledLanguage, bundledLanguages, createHighlighter, type Highlighter } from 'shiki';\nimport { stripMarkdownFormattingFromImageAltText } from './markdown-sanitize';\n\nexport interface DocsMarkdownProps {\n content: string;\n className?: string;\n renderImage?: (props: {\n src: string;\n alt: string;\n title?: string;\n loading?: 'eager' | 'lazy';\n }) => ReactNode;\n}\n\nconst defaultClassName = 'cms-docs-markdown';\nlet markdownInitPromise: Promise<void> | undefined;\n\n// Pre-load common languages for fast cold-start\n// Less common languages are lazy-loaded on demand\nconst PRELOADED_LANGS: BundledLanguage[] = [\n // Web\n 'typescript',\n 'javascript',\n 'tsx',\n 'jsx',\n 'html',\n 'css',\n 'scss',\n // Systems\n 'c',\n 'cpp',\n 'rust',\n 'go',\n // Enterprise\n 'java',\n 'csharp',\n 'kotlin',\n 'swift',\n // Scripting\n 'python',\n 'ruby',\n 'php',\n 'bash',\n 'shellscript',\n 'powershell',\n // Data\n 'json',\n 'yaml',\n 'toml',\n 'xml',\n 'sql',\n 'graphql',\n // Other\n 'markdown',\n 'dockerfile',\n];\n\n// Cache the highlighter instance to avoid re-loading languages/themes\nlet highlighterPromise: Promise<Highlighter> | undefined;\n\n// Track which languages have been loaded\nconst loadedLanguages = new Set<string>(PRELOADED_LANGS);\n\nasync function getHighlighter(): Promise<Highlighter> {\n highlighterPromise ??= createHighlighter({\n themes: ['github-dark'],\n langs: PRELOADED_LANGS,\n });\n return highlighterPromise;\n}\n\n/**\n * Ensures a language is loaded, lazy-loading if needed.\n * Returns the resolved language name, or null if not a valid language.\n */\nasync function ensureLanguageLoaded(\n highlighter: Highlighter,\n lang: string\n): Promise<BundledLanguage | null> {\n // Already loaded\n if (loadedLanguages.has(lang)) {\n return lang as BundledLanguage;\n }\n\n // Check if it's a valid bundled language\n if (lang in bundledLanguages) {\n try {\n await highlighter.loadLanguage(lang as BundledLanguage);\n loadedLanguages.add(lang);\n return lang as BundledLanguage;\n } catch {\n // Loading failed, will render as plain code\n return null;\n }\n }\n\n // Unknown language, will render as plain code\n return null;\n}\n\n// Pre-warm WASM and Shiki at module load to avoid cold start penalty\n// This runs once when the module is first imported (server startup)\nif (typeof window === 'undefined') {\n // Server-side only: trigger initialization immediately\n markdownInitPromise = initMarkdownWasm();\n highlighterPromise = createHighlighter({\n themes: ['github-dark'],\n langs: PRELOADED_LANGS,\n });\n // Log pre-warming for debugging\n Promise.all([markdownInitPromise, highlighterPromise])\n .then(() =>\n console.log(`[docs-markdown] WASM and Shiki pre-warmed (${PRELOADED_LANGS.length} languages)`)\n )\n .catch((err) => console.error('[docs-markdown] Pre-warm failed:', err));\n}\n\nexport function markdownStartsWithHeading(markdown: string): boolean {\n return /^\\s*#\\s+/.test(markdown);\n}\n\nexport async function initMarkdown(): Promise<void> {\n markdownInitPromise ??= initMarkdownWasm();\n await markdownInitPromise;\n}\n\nfunction isTextNode(node: string | MDNode): node is string {\n return typeof node === 'string';\n}\n\nfunction buildComponentProps(\n type: number,\n props?: Record<string, unknown>\n): Record<string, unknown> {\n const result = { ...props };\n const headingLevel = getHeadingLevel(type as NodeType);\n\n if (headingLevel !== undefined) {\n result.level = headingLevel;\n }\n\n return result;\n}\n\nfunction normalizeLanguage(lang: unknown): string {\n if (typeof lang !== 'string') {\n return '';\n }\n\n const normalized = lang.trim().toLowerCase();\n return normalized;\n}\n\nfunction decodeHtmlEntities(value: string): string {\n return value\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('"', '\"')\n .replaceAll(''', \"'\")\n .replaceAll('&', '&');\n}\n\nasync function renderHighlightedCode(code: string, lang: unknown): Promise<ReactNode> {\n const language = normalizeLanguage(lang);\n const displayLanguage = language || 'text';\n\n try {\n const highlighter = await getHighlighter();\n // Ensure language is loaded (lazy-load if needed)\n const resolvedLang = await ensureLanguageLoaded(highlighter, language);\n\n // If language not supported, render plain code\n if (!resolvedLang) {\n return (\n <pre className={`language-${displayLanguage}`}>\n <code className={`language-${displayLanguage}`}>{code}</code>\n </pre>\n );\n }\n\n const result = highlighter.codeToTokens(code, {\n lang: resolvedLang,\n theme: 'github-dark',\n });\n\n return (\n <pre\n className={`shiki language-${displayLanguage}`}\n style={{ backgroundColor: result.bg, color: result.fg }}\n >\n <code className={`shiki_code language-${displayLanguage}`} data-language={displayLanguage}>\n {(() => {\n const lineOccurrences = new Map<string, number>();\n\n return result.tokens.map((line, lineIndex) => {\n const isLastLine = lineIndex === result.tokens.length - 1;\n const lineSignature = line\n .map((token) => `${token.offset}:${token.color ?? ''}:${token.content}`)\n .join('|');\n const lineOccurrence = lineOccurrences.get(lineSignature) ?? 0;\n\n lineOccurrences.set(lineSignature, lineOccurrence + 1);\n\n let tokenCursor = 0;\n\n return (\n <Fragment key={`${lineSignature}#${lineOccurrence}`}>\n {line.map((token) => {\n const tokenKey = `${token.offset}:${token.color ?? ''}:${tokenCursor}:${token.content}`;\n tokenCursor += token.content.length;\n\n return (\n <span key={tokenKey} style={{ color: token.color }}>\n {token.content}\n </span>\n );\n })}\n {isLastLine ? null : '\\n'}\n </Fragment>\n );\n });\n })()}\n </code>\n </pre>\n );\n } catch {\n return (\n <pre className={`language-${displayLanguage}`}>\n <code className={`language-${displayLanguage}`}>{code}</code>\n </pre>\n );\n }\n}\n\nasync function renderNode(\n node: string | MDNode,\n components: ComponentMap,\n renderImage: DocsMarkdownProps['renderImage'],\n imageIndexRef: { current: number },\n key?: string | number\n): Promise<ReactNode> {\n if (isTextNode(node)) {\n return decodeHtmlEntities(node);\n }\n\n const { type, props, children } = node;\n\n if (type === NodeType.IMG && renderImage) {\n const imageIndex = imageIndexRef.current;\n imageIndexRef.current += 1;\n\n return (\n <Fragment key={key}>\n {renderImage({\n src: typeof props?.src === 'string' ? props.src : '',\n alt: typeof props?.alt === 'string' ? props.alt : '',\n title: typeof props?.title === 'string' ? props.title : undefined,\n loading: imageIndex === 0 ? 'eager' : 'lazy',\n })}\n </Fragment>\n );\n }\n\n if (type === NodeType.CODE_BLOCK) {\n const rawCode = children?.filter(isTextNode).join('') ?? '';\n const code = decodeHtmlEntities(rawCode);\n return <Fragment key={key}>{await renderHighlightedCode(code, props?.lang)}</Fragment>;\n }\n\n const renderedChildren = children\n ? await Promise.all(\n children.map((child, index) =>\n renderNode(child, components, renderImage, imageIndexRef, index)\n )\n )\n : undefined;\n\n const Component = components[type as NodeType];\n\n if (Component) {\n const componentProps = buildComponentProps(type, props);\n return (\n <Fragment key={key}>{Component({ ...componentProps, children: renderedChildren })}</Fragment>\n );\n }\n\n return <Fragment key={key}>{renderedChildren}</Fragment>;\n}\n\nexport async function DocsMarkdown({\n content,\n className = defaultClassName,\n renderImage,\n}: DocsMarkdownProps) {\n await initMarkdown();\n\n const ast = mdToJSON(stripMarkdownFormattingFromImageAltText(content));\n const imageIndexRef = { current: 0 };\n const rendered = await Promise.all(\n ast.children.map((node, index) =>\n renderNode(node, defaultComponents, renderImage, imageIndexRef, index)\n )\n );\n\n const resolvedClassName =\n className === defaultClassName ? defaultClassName : `${defaultClassName} ${className}`.trim();\n\n return <div className={resolvedClassName}>{rendered}</div>;\n}\n","/**\n * String-based markdown sanitization helpers (no regex backtracking on user input).\n */\n\nconst DANGEROUS_SCHEMES = ['javascript:', 'vbscript:', 'data:'] as const;\n\nconst HTML_URL_ATTRIBUTES = [\n 'href',\n 'src',\n 'action',\n 'formaction',\n 'cite',\n 'background',\n 'poster',\n 'xlink:href',\n] as const;\n\nfunction stripMarkdownFormattingMarkers(value: string): string {\n return value\n .replaceAll('`', '')\n .replaceAll('**', '')\n .replaceAll('__', '')\n .replaceAll('~~', '')\n .replaceAll('*', '')\n .replaceAll('_', '');\n}\n\nfunction isHtmlWhitespace(ch: string): boolean {\n return ch === ' ' || ch === '\\t' || ch === '\\n' || ch === '\\r' || ch === '\\f' || ch === '\\v';\n}\n\nfunction findUnescaped(content: string, char: string, from: number): number {\n let index = from;\n\n while (index < content.length) {\n const found = content.indexOf(char, index);\n if (found === -1) return -1;\n\n let backslashCount = 0;\n for (let i = found - 1; i >= 0 && content.charAt(i) === '\\\\'; i -= 1) {\n backslashCount += 1;\n }\n\n if (backslashCount % 2 === 0) return found;\n index = found + 1;\n }\n\n return -1;\n}\n\nexport function stripMarkdownFormattingFromImageAltText(content: string): string {\n let result = content;\n let index = 0;\n\n while (index < result.length) {\n const imageStart = result.indexOf('![', index);\n if (imageStart === -1) break;\n\n const altStart = imageStart + 2;\n const altEnd = findUnescaped(result, ']', altStart);\n if (altEnd === -1) {\n index = altStart;\n continue;\n }\n\n if (result.charAt(altEnd + 1) !== '(') {\n index = altEnd + 1;\n continue;\n }\n\n const altText = result.slice(altStart, altEnd);\n const plainAltText = stripMarkdownFormattingMarkers(altText);\n if (plainAltText === altText) {\n index = altEnd + 2;\n continue;\n }\n\n result = result.slice(0, altStart) + plainAltText + result.slice(altEnd);\n index = altStart + plainAltText.length + 2;\n }\n\n return result;\n}\n\nfunction neutralizeDangerousSchemesInUrl(url: string): string {\n let result = url;\n let lower = result.toLowerCase();\n\n for (const scheme of DANGEROUS_SCHEMES) {\n const lowerScheme = scheme.toLowerCase();\n let index = lower.indexOf(lowerScheme);\n\n while (index !== -1) {\n const atUrlStart = index === 0;\n const afterSeparator =\n index > 0 &&\n (isHtmlWhitespace(result.charAt(index - 1)) || result.charAt(index - 1) === '\"');\n\n if (atUrlStart || afterSeparator) {\n result = `${result.slice(0, index)}removed:${result.slice(index + scheme.length)}`;\n lower = result.toLowerCase();\n index = lower.indexOf(lowerScheme, index + 'removed:'.length);\n } else {\n index = lower.indexOf(lowerScheme, index + 1);\n }\n }\n }\n\n return result;\n}\n\nexport function stripScriptTags(content: string): string {\n let result = content;\n let lower = result.toLowerCase();\n let searchFrom = 0;\n\n while (searchFrom < lower.length) {\n const start = lower.indexOf('<script', searchFrom);\n if (start === -1) break;\n\n const closeStart = lower.indexOf('</script', start);\n if (closeStart === -1) {\n // No closing tag — remove the opening <script...> up to the next >\n let end = lower.indexOf('>', start);\n if (end === -1) {\n end = lower.length;\n } else {\n end += 1;\n }\n result = result.slice(0, start) + result.slice(end);\n lower = result.toLowerCase();\n searchFrom = start;\n continue;\n }\n\n let end = lower.indexOf('>', closeStart);\n if (end === -1) {\n searchFrom = start + 1;\n continue;\n }\n end += 1;\n\n result = result.slice(0, start) + result.slice(end);\n lower = result.toLowerCase();\n searchFrom = start;\n }\n\n return result;\n}\n\nfunction replaceUrlInParenGroup(\n content: string,\n openParen: number,\n closeParen: number\n): { content: string; nextIndex: number } {\n const url = content.slice(openParen + 1, closeParen);\n const sanitized = neutralizeDangerousSchemesInUrl(url);\n if (sanitized === url) {\n return { content, nextIndex: closeParen + 1 };\n }\n return {\n content: content.slice(0, openParen + 1) + sanitized + content.slice(closeParen),\n nextIndex: openParen + sanitized.length + 1,\n };\n}\n\nfunction neutralizeMarkdownLinkAndImageDestinations(content: string): string {\n let result = content;\n let index = 0;\n\n while (index < result.length) {\n const isImage = result.startsWith('![', index);\n const marker = isImage ? '![' : '[';\n const markerIndex = isImage ? index : result.indexOf('[', index);\n\n if (markerIndex === -1) break;\n if (!isImage && result.startsWith('![', markerIndex)) {\n index = markerIndex + 2;\n continue;\n }\n\n const bracketStart = isImage ? markerIndex + 2 : markerIndex + 1;\n const closeBracket = result.indexOf(']', bracketStart);\n if (closeBracket === -1) {\n index = markerIndex + marker.length;\n continue;\n }\n\n const openParen = result.indexOf('(', closeBracket);\n if (openParen !== closeBracket + 1) {\n index = markerIndex + marker.length;\n continue;\n }\n\n const closeParen = result.indexOf(')', openParen);\n if (closeParen === -1) {\n // Malformed link with no closing paren — still neutralize dangerous schemes\n let urlEnd = openParen + 1;\n while (\n urlEnd < result.length &&\n !isHtmlWhitespace(result.charAt(urlEnd)) &&\n result.charAt(urlEnd) !== '\\n' &&\n result.charAt(urlEnd) !== '\\r'\n ) {\n urlEnd += 1;\n }\n const url = result.slice(openParen + 1, urlEnd);\n const sanitized = neutralizeDangerousSchemesInUrl(url);\n if (sanitized !== url) {\n result = result.slice(0, openParen + 1) + sanitized + result.slice(urlEnd);\n }\n index = openParen + sanitized.length + 1;\n continue;\n }\n\n const replaced = replaceUrlInParenGroup(result, openParen, closeParen);\n result = replaced.content;\n index = replaced.nextIndex;\n }\n\n return result;\n}\n\nfunction readHtmlAttributeValue(content: string, valueStart: number): number {\n let end = valueStart;\n while (end < content.length && isHtmlWhitespace(content.charAt(end))) {\n end += 1;\n }\n\n const quote = content.charAt(end);\n if (quote === '\"' || quote === \"'\") {\n end += 1;\n while (end < content.length && content.charAt(end) !== quote) {\n end += 1;\n }\n if (end < content.length) end += 1;\n return end;\n }\n\n while (end < content.length) {\n const ch = content.charAt(end);\n if (isHtmlWhitespace(ch) || ch === '>') break;\n end += 1;\n }\n return end;\n}\n\nfunction findHtmlAttribute(\n _content: string,\n lower: string,\n attrName: string,\n from: number\n): number {\n const needle = attrName.toLowerCase();\n let i = from;\n\n while (i < lower.length) {\n const idx = lower.indexOf(needle, i);\n if (idx === -1) return -1;\n\n const before = idx > 0 ? lower.charAt(idx - 1) : '';\n if (idx > 0 && !isHtmlWhitespace(before) && before !== '<' && before !== '/') {\n i = idx + 1;\n continue;\n }\n\n let after = idx + needle.length;\n while (after < lower.length && isHtmlWhitespace(lower.charAt(after))) {\n after += 1;\n }\n\n if (lower.charAt(after) === '=') return idx;\n i = idx + 1;\n }\n\n return -1;\n}\n\nfunction neutralizeHtmlUrlAttributes(content: string): string {\n let result = content;\n let lower = result.toLowerCase();\n\n for (const attr of HTML_URL_ATTRIBUTES) {\n let searchFrom = 0;\n\n while (searchFrom < lower.length) {\n const attrIndex = findHtmlAttribute(result, lower, attr, searchFrom);\n if (attrIndex === -1) break;\n\n let valueStart = attrIndex + attr.length;\n while (valueStart < lower.length && isHtmlWhitespace(lower.charAt(valueStart))) {\n valueStart += 1;\n }\n if (lower.charAt(valueStart) !== '=') {\n searchFrom = attrIndex + 1;\n continue;\n }\n valueStart += 1;\n while (valueStart < result.length && isHtmlWhitespace(result.charAt(valueStart))) {\n valueStart += 1;\n }\n\n const valueEnd = readHtmlAttributeValue(result, valueStart);\n const rawValue = result.slice(valueStart, valueEnd);\n const quote =\n rawValue.charAt(0) === '\"' || rawValue.charAt(0) === \"'\" ? rawValue.charAt(0) : null;\n const innerStart = quote ? 1 : 0;\n const innerEnd =\n quote && rawValue.charAt(rawValue.length - 1) === quote\n ? rawValue.length - 1\n : rawValue.length;\n const url = rawValue.slice(innerStart, innerEnd);\n const sanitized = neutralizeDangerousSchemesInUrl(url);\n const wrapped = quote ? `${quote}${sanitized}${quote}` : sanitized;\n\n if (sanitized !== url) {\n result = result.slice(0, valueStart) + wrapped + result.slice(valueEnd);\n lower = result.toLowerCase();\n }\n\n searchFrom = valueStart + (sanitized !== url ? wrapped.length : rawValue.length);\n }\n }\n\n return result;\n}\n\nfunction stripEventHandlersFromTag(tag: string): string {\n let result = tag;\n let lower = result.toLowerCase();\n let searchFrom = 0;\n\n while (searchFrom < lower.length) {\n let eventStart = -1;\n\n for (let j = searchFrom; j < lower.length; j++) {\n if (lower.charAt(j) !== 'o' || !lower.startsWith('on', j)) continue;\n\n if (j > 0 && !isHtmlWhitespace(lower.charAt(j - 1))) continue;\n\n let nameEnd = j + 2;\n while (nameEnd < lower.length) {\n const ch = lower.charAt(nameEnd);\n if (!((ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch === '-')) break;\n nameEnd += 1;\n }\n\n let eqIndex = nameEnd;\n while (eqIndex < lower.length && isHtmlWhitespace(lower.charAt(eqIndex))) {\n eqIndex += 1;\n }\n\n if (lower.charAt(eqIndex) !== '=' || nameEnd - j < 3) continue;\n\n eventStart = j;\n break;\n }\n\n if (eventStart === -1) break;\n\n const valueEnd = readHtmlAttributeValue(result, lower.indexOf('=', eventStart) + 1);\n result = result.slice(0, eventStart) + result.slice(valueEnd);\n lower = result.toLowerCase();\n searchFrom = eventStart;\n }\n\n return result;\n}\n\nexport function stripInlineEventHandlers(content: string): string {\n let result = content;\n let i = 0;\n\n while (i < result.length) {\n const tagStart = result.indexOf('<', i);\n if (tagStart === -1) break;\n\n if (result.startsWith('<!--', tagStart)) {\n const commentEnd = result.indexOf('-->', tagStart);\n i = commentEnd === -1 ? result.length : commentEnd + 3;\n continue;\n }\n\n const tagEnd = result.indexOf('>', tagStart);\n if (tagEnd === -1) break;\n\n const tag = result.slice(tagStart, tagEnd + 1);\n const sanitizedTag = stripEventHandlersFromTag(tag);\n result = result.slice(0, tagStart) + sanitizedTag + result.slice(tagEnd + 1);\n i = tagStart + sanitizedTag.length;\n }\n\n return result;\n}\n\nexport function sanitizeMarkdownContent(content: string): string {\n let sanitized = stripMarkdownFormattingFromImageAltText(content);\n sanitized = stripScriptTags(sanitized);\n sanitized = neutralizeMarkdownLinkAndImageDestinations(sanitized);\n sanitized = neutralizeHtmlUrlAttributes(sanitized);\n return stripInlineEventHandlers(sanitized);\n}\n\nexport function containsDangerousScheme(content: string, scheme: string): boolean {\n return content.toLowerCase().includes(scheme.toLowerCase());\n}\n"],"mappings":"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","names":["init","mdToJSON","NodeType","NodeType","Fragment","jsx","Fragment","jsx","jsxs","initMarkdown","isTextNode","Fragment","NodeType","mdToJSON"]}
|
|
@@ -13,9 +13,53 @@ var HTML_URL_ATTRIBUTES = [
|
|
|
13
13
|
"poster",
|
|
14
14
|
"xlink:href"
|
|
15
15
|
];
|
|
16
|
+
function stripMarkdownFormattingMarkers(value) {
|
|
17
|
+
return value.replaceAll("`", "").replaceAll("**", "").replaceAll("__", "").replaceAll("~~", "").replaceAll("*", "").replaceAll("_", "");
|
|
18
|
+
}
|
|
16
19
|
function isHtmlWhitespace(ch) {
|
|
17
20
|
return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f" || ch === "\v";
|
|
18
21
|
}
|
|
22
|
+
function findUnescaped(content, char, from) {
|
|
23
|
+
let index = from;
|
|
24
|
+
while (index < content.length) {
|
|
25
|
+
const found = content.indexOf(char, index);
|
|
26
|
+
if (found === -1) return -1;
|
|
27
|
+
let backslashCount = 0;
|
|
28
|
+
for (let i = found - 1; i >= 0 && content.charAt(i) === "\\"; i -= 1) {
|
|
29
|
+
backslashCount += 1;
|
|
30
|
+
}
|
|
31
|
+
if (backslashCount % 2 === 0) return found;
|
|
32
|
+
index = found + 1;
|
|
33
|
+
}
|
|
34
|
+
return -1;
|
|
35
|
+
}
|
|
36
|
+
function stripMarkdownFormattingFromImageAltText(content) {
|
|
37
|
+
let result = content;
|
|
38
|
+
let index = 0;
|
|
39
|
+
while (index < result.length) {
|
|
40
|
+
const imageStart = result.indexOf("![", index);
|
|
41
|
+
if (imageStart === -1) break;
|
|
42
|
+
const altStart = imageStart + 2;
|
|
43
|
+
const altEnd = findUnescaped(result, "]", altStart);
|
|
44
|
+
if (altEnd === -1) {
|
|
45
|
+
index = altStart;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (result.charAt(altEnd + 1) !== "(") {
|
|
49
|
+
index = altEnd + 1;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const altText = result.slice(altStart, altEnd);
|
|
53
|
+
const plainAltText = stripMarkdownFormattingMarkers(altText);
|
|
54
|
+
if (plainAltText === altText) {
|
|
55
|
+
index = altEnd + 2;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
result = result.slice(0, altStart) + plainAltText + result.slice(altEnd);
|
|
59
|
+
index = altStart + plainAltText.length + 2;
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
19
63
|
function neutralizeDangerousSchemesInUrl(url) {
|
|
20
64
|
let result = url;
|
|
21
65
|
let lower = result.toLowerCase();
|
|
@@ -252,7 +296,8 @@ function stripInlineEventHandlers(content) {
|
|
|
252
296
|
return result;
|
|
253
297
|
}
|
|
254
298
|
function sanitizeMarkdownContent(content) {
|
|
255
|
-
let sanitized =
|
|
299
|
+
let sanitized = stripMarkdownFormattingFromImageAltText(content);
|
|
300
|
+
sanitized = stripScriptTags(sanitized);
|
|
256
301
|
sanitized = neutralizeMarkdownLinkAndImageDestinations(sanitized);
|
|
257
302
|
sanitized = neutralizeHtmlUrlAttributes(sanitized);
|
|
258
303
|
return stripInlineEventHandlers(sanitized);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../lib/markdown-utils.ts","../../lib/markdown-sanitize.ts","../../lib/result.ts"],"sourcesContent":["/**\n * Markdown Parsing Utilities\n *\n * Three implementations showing the tradeoffs between simple and more\n * defensive parsing:\n * - v1 (Naive): Direct call, crashes on bad input\n * - v2 (Defensive): Try-catch with null fallback\n * - v3 (Robust): Result type, validation, lightweight sanitization\n *\n * The default export points to the v3 implementation.\n */\n\nimport type { MDTree } from 'md4w';\nimport { mdToJSON } from 'md4w';\n\nimport { sanitizeMarkdownContent } from './markdown-sanitize';\nimport { err, ok, type Result } from './result';\n\n// -----------------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------------\n\n/**\n * Error codes for markdown parsing failures.\n */\nexport const ParseErrorCode = {\n INVALID_INPUT: 'INVALID_INPUT',\n EMPTY_CONTENT: 'EMPTY_CONTENT',\n PARSE_FAILED: 'PARSE_FAILED',\n CONTENT_TOO_LONG: 'CONTENT_TOO_LONG',\n NESTED_TOO_DEEP: 'NESTED_TOO_DEEP',\n} as const;\n\nexport type ParseErrorCode = (typeof ParseErrorCode)[keyof typeof ParseErrorCode];\n\n/**\n * Structured error for markdown parsing failures.\n */\nexport interface ParseError {\n code: ParseErrorCode;\n message: string;\n cause?: Error;\n}\n\n/**\n * Options for robust markdown parsing.\n */\nexport interface ParseOptions {\n /**\n * Maximum content length in characters.\n * @default 500000 (500KB of text, ~100K words)\n */\n maxLength?: number;\n\n /**\n * Whether to run a lightweight string-based sanitization pass before parsing.\n * @default true\n */\n sanitize?: boolean;\n\n /**\n * Whether to allow empty content.\n * @default false\n */\n allowEmpty?: boolean;\n}\n\n// -----------------------------------------------------------------------------\n// Constants\n// -----------------------------------------------------------------------------\n\nconst DEFAULT_MAX_LENGTH = 500_000; // 500KB of text\n\n/**\n * Patterns that indicate potential XSS attempts in markdown.\n * These are checked in raw content before parsing.\n */\nconst XSS_PATTERNS = [\n /<script\\b/i,\n /javascript:/i,\n /on\\w+\\s*=/i, // onclick=, onerror=, etc.\n /\\bdata:/i, // data: URIs can be dangerous\n /vbscript:/i,\n] as const;\n\n// -----------------------------------------------------------------------------\n// V1: Naive Implementation\n// -----------------------------------------------------------------------------\n\n/**\n * Naive markdown parser - crashes on bad input.\n *\n * DO NOT USE IN PRODUCTION. This is for demonstration only.\n *\n * Problems:\n * - No input validation\n * - No error handling\n * - Will crash the entire app on malformed input\n * - No protection against XSS\n * - No content limits\n *\n * @example\n * ```ts\n * // This crashes if content is null, undefined, or malformed\n * const ast = parseMarkdownV1(userInput);\n * ```\n */\nexport function parseMarkdownV1(content: string): MDTree {\n return mdToJSON(content);\n}\n\n// -----------------------------------------------------------------------------\n// V2: Defensive Implementation\n// -----------------------------------------------------------------------------\n\n/**\n * Defensive markdown parser - catches errors but loses context.\n *\n * Better than v1 but still problematic:\n * - Returns null on any error (loses error details)\n * - Caller can't distinguish between empty content and parse failure\n * - Still no XSS protection\n * - Still no content limits\n *\n * @example\n * ```ts\n * const ast = parseMarkdownV2(userInput);\n * if (!ast) {\n * // What went wrong? We don't know.\n * return <ErrorFallback />;\n * }\n * ```\n */\nexport function parseMarkdownV2(content: string): MDTree | null {\n try {\n // Basic null check\n if (content == null) {\n return null;\n }\n return mdToJSON(content);\n } catch {\n return null;\n }\n}\n\n// -----------------------------------------------------------------------------\n// V3: Robust Implementation\n// -----------------------------------------------------------------------------\n\n/**\n * Creates a ParseError with the given code and message.\n */\nfunction createParseError(code: ParseErrorCode, message: string, cause?: Error): ParseError {\n return { code, message, cause };\n}\n\n/**\n * Checks content for potential XSS patterns.\n * Returns the first matched pattern or null if clean.\n */\nfunction detectXssPattern(content: string): RegExp | null {\n for (const pattern of XSS_PATTERNS) {\n if (pattern.test(content)) {\n return pattern;\n }\n }\n return null;\n}\n\n/**\n * Sanitizes content by removing dangerous patterns.\n * This is a lightweight, string-based sanitizer and not a full HTML sanitizer.\n */\nfunction sanitizeContent(content: string): string {\n return sanitizeMarkdownContent(content);\n}\n\n/**\n * Robust markdown parser with explicit error handling and lightweight sanitization.\n *\n * Features:\n * - Input validation with specific error codes\n * - Result type for explicit error handling\n * - XSS pattern detection and optional lightweight sanitization\n * - Content length limits\n * - Preserves error context for debugging\n *\n * @param content - Markdown string to parse\n * @param options - Parsing options\n * @returns Result containing the AST or a structured error\n *\n * @example\n * ```ts\n * const result = parseMarkdownV3(userInput);\n *\n * if (!result.ok) {\n * switch (result.error.code) {\n * case 'INVALID_INPUT':\n * return <InvalidInputError message={result.error.message} />;\n * case 'CONTENT_TOO_LONG':\n * return <ContentTooLongError />;\n * case 'PARSE_FAILED':\n * console.error('Parse error:', result.error.cause);\n * return <ParseError />;\n * default:\n * return <GenericError />;\n * }\n * }\n *\n * return <MarkdownRenderer ast={result.value} />;\n * ```\n */\nexport function parseMarkdownV3(\n content: string,\n options: ParseOptions = {}\n): Result<MDTree, ParseError> {\n const { maxLength = DEFAULT_MAX_LENGTH, sanitize = true, allowEmpty = false } = options;\n\n // 1. Validate input type\n if (content === null || content === undefined) {\n return err(\n createParseError(\n ParseErrorCode.INVALID_INPUT,\n `Content must be a string, received ${content === null ? 'null' : 'undefined'}`\n )\n );\n }\n\n if (typeof content !== 'string') {\n return err(\n createParseError(\n ParseErrorCode.INVALID_INPUT,\n `Content must be a string, received ${typeof content}`\n )\n );\n }\n\n // 2. Handle empty content\n const trimmed = content.trim();\n if (trimmed.length === 0 && !allowEmpty) {\n return err(\n createParseError(ParseErrorCode.EMPTY_CONTENT, 'Content is empty or contains only whitespace')\n );\n }\n\n // 3. Check content length\n if (content.length > maxLength) {\n return err(\n createParseError(\n ParseErrorCode.CONTENT_TOO_LONG,\n `Content exceeds maximum length of ${maxLength.toLocaleString()} characters ` +\n `(received ${content.length.toLocaleString()})`\n )\n );\n }\n\n // 4. Check for XSS patterns\n const xssPattern = detectXssPattern(content);\n if (xssPattern) {\n if (process.env.NODE_ENV === 'development') {\n console.warn(\n `[parseMarkdownV3] XSS pattern detected: ${xssPattern.toString()}`,\n sanitize ? 'Content will be sanitized.' : 'Sanitization is disabled!'\n );\n }\n }\n\n // 5. Optionally sanitize content\n const processedContent = sanitize ? sanitizeContent(content) : content;\n\n // 6. Parse markdown with error handling\n try {\n const ast = mdToJSON(processedContent);\n return ok(ast);\n } catch (error) {\n const cause = error instanceof Error ? error : new Error(String(error));\n return err(\n createParseError(\n ParseErrorCode.PARSE_FAILED,\n `Failed to parse markdown: ${cause.message}`,\n cause\n )\n );\n }\n}\n\n// -----------------------------------------------------------------------------\n// Default Export\n// -----------------------------------------------------------------------------\n\n/**\n * Default markdown parser.\n *\n * This is an alias for `parseMarkdownV3`, the most defensive implementation\n * in this module.\n *\n * @example\n * ```ts\n * import { parseMarkdown } from '@/lib/markdown-utils';\n *\n * const result = parseMarkdown(content);\n * if (!result.ok) {\n * // Handle error with full context\n * console.error(`[${result.error.code}] ${result.error.message}`);\n * return null;\n * }\n * return result.value;\n * ```\n */\nexport const parseMarkdown = parseMarkdownV3;\n\n// -----------------------------------------------------------------------------\n// Utility Functions\n// -----------------------------------------------------------------------------\n\n/**\n * Checks if content is safe markdown (no XSS patterns detected).\n *\n * @param content - Markdown string to check\n * @returns true if no XSS patterns detected\n */\nexport function isSafeMarkdown(content: string): boolean {\n return detectXssPattern(content) === null;\n}\n\n/**\n * Estimates the word count of markdown content.\n * Useful for content length limits and reading time estimates.\n *\n * @param content - Markdown string to count\n * @returns Estimated word count\n */\nconst MAX_ESTIMATE_LENGTH = 500_000;\n\nfunction stripFencedCodeBlocks(text: string): string {\n let result = text;\n let start = result.indexOf('```');\n while (start !== -1) {\n const end = result.indexOf('```', start + 3);\n if (end === -1) break;\n result = result.slice(0, start) + result.slice(end + 3);\n start = result.indexOf('```');\n }\n return result;\n}\n\nfunction stripInlineCodeSpans(text: string): string {\n let result = text;\n let start = result.indexOf('`');\n while (start !== -1) {\n const end = result.indexOf('`', start + 1);\n if (end === -1) break;\n result = result.slice(0, start) + result.slice(end + 1);\n start = result.indexOf('`');\n }\n return result;\n}\n\nfunction stripMarkdownImages(text: string): string {\n let result = text;\n let index = result.indexOf('![');\n while (index !== -1) {\n const closeBracket = result.indexOf(']', index + 2);\n const openParen = closeBracket !== -1 ? result.indexOf('(', closeBracket) : -1;\n const closeParen = openParen !== -1 ? result.indexOf(')', openParen) : -1;\n if (closeBracket === -1 || openParen === -1 || closeParen === -1) break;\n result = result.slice(0, index) + result.slice(closeParen + 1);\n index = result.indexOf('![');\n }\n return result;\n}\n\nfunction convertMarkdownLinksToText(text: string): string {\n let result = text;\n let index = result.indexOf('[');\n while (index !== -1) {\n const closeBracket = result.indexOf(']', index + 1);\n const openParen = closeBracket !== -1 ? result.indexOf('(', closeBracket) : -1;\n const closeParen = openParen !== -1 ? result.indexOf(')', openParen) : -1;\n if (closeBracket === -1 || openParen === -1 || closeParen === -1) break;\n const label = result.slice(index + 1, closeBracket);\n result = result.slice(0, index) + label + result.slice(closeParen + 1);\n index = result.indexOf('[', index + label.length);\n }\n return result;\n}\n\nexport function estimateWordCount(content: string): number {\n const bounded =\n content.length > MAX_ESTIMATE_LENGTH ? content.slice(0, MAX_ESTIMATE_LENGTH) : content;\n\n let plainText = bounded;\n plainText = plainText.replace(/#+\\s/g, '');\n plainText = stripFencedCodeBlocks(plainText);\n plainText = stripInlineCodeSpans(plainText);\n plainText = stripMarkdownImages(plainText);\n plainText = convertMarkdownLinksToText(plainText);\n plainText = plainText.replace(/\\*\\*|__|~~/g, '');\n\n const words = plainText.trim().split(/\\s+/).filter(Boolean);\n return words.length;\n}\n\n/**\n * Estimates reading time for markdown content.\n *\n * @param content - Markdown string\n * @param wordsPerMinute - Reading speed (default 200 WPM)\n * @returns Reading time in minutes\n */\nexport function estimateReadingTime(content: string, wordsPerMinute = 200): number {\n const wordCount = estimateWordCount(content);\n return Math.ceil(wordCount / wordsPerMinute);\n}\n","/**\n * String-based markdown sanitization helpers (no regex backtracking on user input).\n */\n\nconst DANGEROUS_SCHEMES = ['javascript:', 'vbscript:', 'data:'] as const;\n\nconst HTML_URL_ATTRIBUTES = [\n 'href',\n 'src',\n 'action',\n 'formaction',\n 'cite',\n 'background',\n 'poster',\n 'xlink:href',\n] as const;\n\nfunction isHtmlWhitespace(ch: string): boolean {\n return ch === ' ' || ch === '\\t' || ch === '\\n' || ch === '\\r' || ch === '\\f' || ch === '\\v';\n}\n\nfunction neutralizeDangerousSchemesInUrl(url: string): string {\n let result = url;\n let lower = result.toLowerCase();\n\n for (const scheme of DANGEROUS_SCHEMES) {\n const lowerScheme = scheme.toLowerCase();\n let index = lower.indexOf(lowerScheme);\n\n while (index !== -1) {\n const atUrlStart = index === 0;\n const afterSeparator =\n index > 0 &&\n (isHtmlWhitespace(result.charAt(index - 1)) || result.charAt(index - 1) === '\"');\n\n if (atUrlStart || afterSeparator) {\n result = `${result.slice(0, index)}removed:${result.slice(index + scheme.length)}`;\n lower = result.toLowerCase();\n index = lower.indexOf(lowerScheme, index + 'removed:'.length);\n } else {\n index = lower.indexOf(lowerScheme, index + 1);\n }\n }\n }\n\n return result;\n}\n\nexport function stripScriptTags(content: string): string {\n let result = content;\n let lower = result.toLowerCase();\n let searchFrom = 0;\n\n while (searchFrom < lower.length) {\n const start = lower.indexOf('<script', searchFrom);\n if (start === -1) break;\n\n const closeStart = lower.indexOf('</script', start);\n if (closeStart === -1) {\n // No closing tag — remove the opening <script...> up to the next >\n let end = lower.indexOf('>', start);\n if (end === -1) {\n end = lower.length;\n } else {\n end += 1;\n }\n result = result.slice(0, start) + result.slice(end);\n lower = result.toLowerCase();\n searchFrom = start;\n continue;\n }\n\n let end = lower.indexOf('>', closeStart);\n if (end === -1) {\n searchFrom = start + 1;\n continue;\n }\n end += 1;\n\n result = result.slice(0, start) + result.slice(end);\n lower = result.toLowerCase();\n searchFrom = start;\n }\n\n return result;\n}\n\nfunction replaceUrlInParenGroup(\n content: string,\n openParen: number,\n closeParen: number\n): { content: string; nextIndex: number } {\n const url = content.slice(openParen + 1, closeParen);\n const sanitized = neutralizeDangerousSchemesInUrl(url);\n if (sanitized === url) {\n return { content, nextIndex: closeParen + 1 };\n }\n return {\n content: content.slice(0, openParen + 1) + sanitized + content.slice(closeParen),\n nextIndex: openParen + sanitized.length + 1,\n };\n}\n\nfunction neutralizeMarkdownLinkAndImageDestinations(content: string): string {\n let result = content;\n let index = 0;\n\n while (index < result.length) {\n const isImage = result.startsWith('![', index);\n const marker = isImage ? '![' : '[';\n const markerIndex = isImage ? index : result.indexOf('[', index);\n\n if (markerIndex === -1) break;\n if (!isImage && result.startsWith('![', markerIndex)) {\n index = markerIndex + 2;\n continue;\n }\n\n const bracketStart = isImage ? markerIndex + 2 : markerIndex + 1;\n const closeBracket = result.indexOf(']', bracketStart);\n if (closeBracket === -1) {\n index = markerIndex + marker.length;\n continue;\n }\n\n const openParen = result.indexOf('(', closeBracket);\n if (openParen !== closeBracket + 1) {\n index = markerIndex + marker.length;\n continue;\n }\n\n const closeParen = result.indexOf(')', openParen);\n if (closeParen === -1) {\n // Malformed link with no closing paren — still neutralize dangerous schemes\n let urlEnd = openParen + 1;\n while (\n urlEnd < result.length &&\n !isHtmlWhitespace(result.charAt(urlEnd)) &&\n result.charAt(urlEnd) !== '\\n' &&\n result.charAt(urlEnd) !== '\\r'\n ) {\n urlEnd += 1;\n }\n const url = result.slice(openParen + 1, urlEnd);\n const sanitized = neutralizeDangerousSchemesInUrl(url);\n if (sanitized !== url) {\n result = result.slice(0, openParen + 1) + sanitized + result.slice(urlEnd);\n }\n index = openParen + sanitized.length + 1;\n continue;\n }\n\n const replaced = replaceUrlInParenGroup(result, openParen, closeParen);\n result = replaced.content;\n index = replaced.nextIndex;\n }\n\n return result;\n}\n\nfunction readHtmlAttributeValue(content: string, valueStart: number): number {\n let end = valueStart;\n while (end < content.length && isHtmlWhitespace(content.charAt(end))) {\n end += 1;\n }\n\n const quote = content.charAt(end);\n if (quote === '\"' || quote === \"'\") {\n end += 1;\n while (end < content.length && content.charAt(end) !== quote) {\n end += 1;\n }\n if (end < content.length) end += 1;\n return end;\n }\n\n while (end < content.length) {\n const ch = content.charAt(end);\n if (isHtmlWhitespace(ch) || ch === '>') break;\n end += 1;\n }\n return end;\n}\n\nfunction findHtmlAttribute(\n _content: string,\n lower: string,\n attrName: string,\n from: number\n): number {\n const needle = attrName.toLowerCase();\n let i = from;\n\n while (i < lower.length) {\n const idx = lower.indexOf(needle, i);\n if (idx === -1) return -1;\n\n const before = idx > 0 ? lower.charAt(idx - 1) : '';\n if (idx > 0 && !isHtmlWhitespace(before) && before !== '<' && before !== '/') {\n i = idx + 1;\n continue;\n }\n\n let after = idx + needle.length;\n while (after < lower.length && isHtmlWhitespace(lower.charAt(after))) {\n after += 1;\n }\n\n if (lower.charAt(after) === '=') return idx;\n i = idx + 1;\n }\n\n return -1;\n}\n\nfunction neutralizeHtmlUrlAttributes(content: string): string {\n let result = content;\n let lower = result.toLowerCase();\n\n for (const attr of HTML_URL_ATTRIBUTES) {\n let searchFrom = 0;\n\n while (searchFrom < lower.length) {\n const attrIndex = findHtmlAttribute(result, lower, attr, searchFrom);\n if (attrIndex === -1) break;\n\n let valueStart = attrIndex + attr.length;\n while (valueStart < lower.length && isHtmlWhitespace(lower.charAt(valueStart))) {\n valueStart += 1;\n }\n if (lower.charAt(valueStart) !== '=') {\n searchFrom = attrIndex + 1;\n continue;\n }\n valueStart += 1;\n while (valueStart < result.length && isHtmlWhitespace(result.charAt(valueStart))) {\n valueStart += 1;\n }\n\n const valueEnd = readHtmlAttributeValue(result, valueStart);\n const rawValue = result.slice(valueStart, valueEnd);\n const quote =\n rawValue.charAt(0) === '\"' || rawValue.charAt(0) === \"'\" ? rawValue.charAt(0) : null;\n const innerStart = quote ? 1 : 0;\n const innerEnd =\n quote && rawValue.charAt(rawValue.length - 1) === quote\n ? rawValue.length - 1\n : rawValue.length;\n const url = rawValue.slice(innerStart, innerEnd);\n const sanitized = neutralizeDangerousSchemesInUrl(url);\n const wrapped = quote ? `${quote}${sanitized}${quote}` : sanitized;\n\n if (sanitized !== url) {\n result = result.slice(0, valueStart) + wrapped + result.slice(valueEnd);\n lower = result.toLowerCase();\n }\n\n searchFrom = valueStart + (sanitized !== url ? wrapped.length : rawValue.length);\n }\n }\n\n return result;\n}\n\nfunction stripEventHandlersFromTag(tag: string): string {\n let result = tag;\n let lower = result.toLowerCase();\n let searchFrom = 0;\n\n while (searchFrom < lower.length) {\n let eventStart = -1;\n\n for (let j = searchFrom; j < lower.length; j++) {\n if (lower.charAt(j) !== 'o' || !lower.startsWith('on', j)) continue;\n\n if (j > 0 && !isHtmlWhitespace(lower.charAt(j - 1))) continue;\n\n let nameEnd = j + 2;\n while (nameEnd < lower.length) {\n const ch = lower.charAt(nameEnd);\n if (!((ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch === '-')) break;\n nameEnd += 1;\n }\n\n let eqIndex = nameEnd;\n while (eqIndex < lower.length && isHtmlWhitespace(lower.charAt(eqIndex))) {\n eqIndex += 1;\n }\n\n if (lower.charAt(eqIndex) !== '=' || nameEnd - j < 3) continue;\n\n eventStart = j;\n break;\n }\n\n if (eventStart === -1) break;\n\n const valueEnd = readHtmlAttributeValue(result, lower.indexOf('=', eventStart) + 1);\n result = result.slice(0, eventStart) + result.slice(valueEnd);\n lower = result.toLowerCase();\n searchFrom = eventStart;\n }\n\n return result;\n}\n\nexport function stripInlineEventHandlers(content: string): string {\n let result = content;\n let i = 0;\n\n while (i < result.length) {\n const tagStart = result.indexOf('<', i);\n if (tagStart === -1) break;\n\n if (result.startsWith('<!--', tagStart)) {\n const commentEnd = result.indexOf('-->', tagStart);\n i = commentEnd === -1 ? result.length : commentEnd + 3;\n continue;\n }\n\n const tagEnd = result.indexOf('>', tagStart);\n if (tagEnd === -1) break;\n\n const tag = result.slice(tagStart, tagEnd + 1);\n const sanitizedTag = stripEventHandlersFromTag(tag);\n result = result.slice(0, tagStart) + sanitizedTag + result.slice(tagEnd + 1);\n i = tagStart + sanitizedTag.length;\n }\n\n return result;\n}\n\nexport function sanitizeMarkdownContent(content: string): string {\n let sanitized = stripScriptTags(content);\n sanitized = neutralizeMarkdownLinkAndImageDestinations(sanitized);\n sanitized = neutralizeHtmlUrlAttributes(sanitized);\n return stripInlineEventHandlers(sanitized);\n}\n\nexport function containsDangerousScheme(content: string, scheme: string): boolean {\n return content.toLowerCase().includes(scheme.toLowerCase());\n}\n","/**\n * Result Type Utilities\n *\n * Go-style error handling with discriminated union types.\n * Provides type-safe success/error handling without exceptions.\n *\n * @example\n * ```ts\n * const [err, data] = await handle(async () => {\n * const response = await fetch(url);\n * if (!response.ok) throw new Error(`HTTP ${response.status}`);\n * return response.json();\n * });\n *\n * if (err) {\n * console.error('Failed:', err.message);\n * return { error: err.message };\n * }\n * return { data };\n * ```\n */\n\n// -----------------------------------------------------------------------------\n// Result Type\n// -----------------------------------------------------------------------------\n\n/**\n * A discriminated union representing either success or failure.\n *\n * @template T - The success value type\n * @template E - The error type (defaults to Error)\n *\n * @example\n * ```ts\n * function divide(a: number, b: number): Result<number, string> {\n * if (b === 0) return err('Division by zero');\n * return ok(a / b);\n * }\n *\n * const result = divide(10, 2);\n * if (isOk(result)) {\n * console.log('Result:', result.value); // 5\n * } else {\n * console.error('Error:', result.error);\n * }\n * ```\n */\nexport type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };\n\n// -----------------------------------------------------------------------------\n// Constructors\n// -----------------------------------------------------------------------------\n\n/**\n * Creates a success Result.\n *\n * @param value - The success value\n * @returns A success Result containing the value\n *\n * @example\n * ```ts\n * const result = ok(42);\n * // { ok: true, value: 42 }\n * ```\n */\nexport function ok<T>(value: T): Result<T, never> {\n return { ok: true, value };\n}\n\n/**\n * Creates a failure Result.\n *\n * @param error - The error value\n * @returns A failure Result containing the error\n *\n * @example\n * ```ts\n * const result = err(new Error('Something went wrong'));\n * // { ok: false, error: Error('Something went wrong') }\n * ```\n */\nexport function err<E>(error: E): Result<never, E> {\n return { ok: false, error };\n}\n\n// -----------------------------------------------------------------------------\n// Type Guards\n// -----------------------------------------------------------------------------\n\n/**\n * Type guard to check if a Result is successful.\n *\n * @param result - The Result to check\n * @returns true if the Result is a success\n *\n * @example\n * ```ts\n * const result = fetchData();\n * if (isOk(result)) {\n * // TypeScript knows result.value exists here\n * console.log(result.value);\n * }\n * ```\n */\nexport function isOk<T, E>(result: Result<T, E>): result is { ok: true; value: T } {\n return result.ok === true;\n}\n\n/**\n * Type guard to check if a Result is a failure.\n *\n * @param result - The Result to check\n * @returns true if the Result is a failure\n *\n * @example\n * ```ts\n * const result = fetchData();\n * if (isErr(result)) {\n * // TypeScript knows result.error exists here\n * console.error(result.error);\n * }\n * ```\n */\nexport function isErr<T, E>(result: Result<T, E>): result is { ok: false; error: E } {\n return result.ok === false;\n}\n\n// -----------------------------------------------------------------------------\n// Extractors\n// -----------------------------------------------------------------------------\n\n/**\n * Extracts the value from a Result, throwing if it's an error.\n *\n * @param result - The Result to unwrap\n * @returns The success value\n * @throws The error if Result is a failure\n *\n * @example\n * ```ts\n * const result = ok(42);\n * const value = unwrap(result); // 42\n *\n * const errorResult = err(new Error('fail'));\n * const value2 = unwrap(errorResult); // throws Error('fail')\n * ```\n */\nexport function unwrap<T, E>(result: Result<T, E>): T {\n if (isOk(result)) {\n return result.value;\n }\n throw result.error;\n}\n\n/**\n * Extracts the value from a Result, returning a default on error.\n *\n * @param result - The Result to unwrap\n * @param defaultValue - The value to return if Result is an error\n * @returns The success value or the default value\n *\n * @example\n * ```ts\n * const result = err(new Error('fail'));\n * const value = unwrapOr(result, 0); // 0\n *\n * const okResult = ok(42);\n * const value2 = unwrapOr(okResult, 0); // 42\n * ```\n */\nexport function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T {\n if (isOk(result)) {\n return result.value;\n }\n return defaultValue;\n}\n\n/**\n * Extracts the value from a Result, computing a default on error.\n *\n * @param result - The Result to unwrap\n * @param fn - Function to compute the default value from the error\n * @returns The success value or the computed default\n *\n * @example\n * ```ts\n * const result = err(new Error('not found'));\n * const value = unwrapOrElse(result, (e) => {\n * console.error('Error:', e.message);\n * return [];\n * });\n * ```\n */\nexport function unwrapOrElse<T, E>(result: Result<T, E>, fn: (error: E) => T): T {\n if (isOk(result)) {\n return result.value;\n }\n return fn(result.error);\n}\n\n// -----------------------------------------------------------------------------\n// Transformers\n// -----------------------------------------------------------------------------\n\n/**\n * Maps a successful Result's value.\n *\n * @param result - The Result to map\n * @param fn - Function to transform the value\n * @returns A new Result with the transformed value, or the original error\n *\n * @example\n * ```ts\n * const result = ok(5);\n * const doubled = map(result, (n) => n * 2); // ok(10)\n *\n * const errorResult = err('fail');\n * const still = map(errorResult, (n) => n * 2); // err('fail')\n * ```\n */\nexport function map<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E> {\n if (isOk(result)) {\n return ok(fn(result.value));\n }\n return result;\n}\n\n/**\n * Maps a failed Result's error.\n *\n * @param result - The Result to map\n * @param fn - Function to transform the error\n * @returns A new Result with the transformed error, or the original value\n *\n * @example\n * ```ts\n * const result = err('not found');\n * const mapped = mapErr(result, (e) => new Error(e)); // err(Error('not found'))\n * ```\n */\nexport function mapErr<T, E, F>(result: Result<T, E>, fn: (error: E) => F): Result<T, F> {\n if (isErr(result)) {\n return err(fn(result.error));\n }\n return result;\n}\n\n/**\n * Chains Result-returning operations.\n *\n * @param result - The Result to chain from\n * @param fn - Function that returns a new Result\n * @returns The chained Result\n *\n * @example\n * ```ts\n * function parse(input: string): Result<number, string> {\n * const n = parseInt(input, 10);\n * return isNaN(n) ? err('not a number') : ok(n);\n * }\n *\n * function double(n: number): Result<number, string> {\n * return ok(n * 2);\n * }\n *\n * const result = flatMap(parse('5'), double); // ok(10)\n * const fail = flatMap(parse('abc'), double); // err('not a number')\n * ```\n */\nexport function flatMap<T, U, E>(\n result: Result<T, E>,\n fn: (value: T) => Result<U, E>\n): Result<U, E> {\n if (isOk(result)) {\n return fn(result.value);\n }\n return result;\n}\n\n// -----------------------------------------------------------------------------\n// Async Helpers\n// -----------------------------------------------------------------------------\n\n/**\n * Wraps a Promise in a Result type.\n *\n * @param promise - The Promise to wrap\n * @returns A Promise that resolves to a Result\n *\n * @example\n * ```ts\n * const result = await fromPromise(fetch('/api/data'));\n * if (isErr(result)) {\n * console.error('Fetch failed:', result.error);\n * return;\n * }\n * const response = result.value;\n * ```\n */\nexport async function fromPromise<T>(promise: Promise<T>): Promise<Result<T, Error>> {\n try {\n const value = await promise;\n return ok(value);\n } catch (error) {\n return err(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\n/**\n * Wraps a throwing function in a Result type.\n *\n * @param fn - The function to wrap\n * @returns A Result containing the return value or the thrown error\n *\n * @example\n * ```ts\n * const result = tryCatch(() => JSON.parse(input));\n * if (isErr(result)) {\n * console.error('Invalid JSON:', result.error);\n * return null;\n * }\n * return result.value;\n * ```\n */\nexport function tryCatch<T>(fn: () => T): Result<T, Error> {\n try {\n return ok(fn());\n } catch (error) {\n return err(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\n/**\n * Wraps an async function in a Result type.\n *\n * @param fn - The async function to wrap\n * @returns A Promise that resolves to a Result\n *\n * @example\n * ```ts\n * const result = await tryCatchAsync(async () => {\n * const response = await fetch('/api/data');\n * if (!response.ok) throw new Error(`HTTP ${response.status}`);\n * return response.json();\n * });\n * ```\n */\nexport async function tryCatchAsync<T>(fn: () => Promise<T>): Promise<Result<T, Error>> {\n try {\n const value = await fn();\n return ok(value);\n } catch (error) {\n return err(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\n// -----------------------------------------------------------------------------\n// Tuple Helpers (Go-style)\n// -----------------------------------------------------------------------------\n\n/**\n * Go-style tuple for error handling: [error, value]\n *\n * @example\n * ```ts\n * const [err, data] = await handle(fetchData);\n * if (err) {\n * console.error(err);\n * return;\n * }\n * console.log(data);\n * ```\n */\nexport type GoTuple<T, E = Error> = [E, undefined] | [undefined, T];\n\n/**\n * Converts a Result to a Go-style tuple.\n *\n * @param result - The Result to convert\n * @returns A tuple of [error, value]\n *\n * @example\n * ```ts\n * const result = await fetchData();\n * const [err, data] = toTuple(result);\n * ```\n */\nexport function toTuple<T, E>(result: Result<T, E>): GoTuple<T, E> {\n if (isOk(result)) {\n return [undefined, result.value];\n }\n return [result.error, undefined];\n}\n\n/**\n * Wraps an async function and returns a Go-style tuple.\n *\n * @param fn - The async function to wrap\n * @returns A Promise that resolves to [error, value] tuple\n *\n * @example\n * ```ts\n * const [err, data] = await handle(async () => {\n * const res = await fetch('/api/users');\n * if (!res.ok) throw new Error(`HTTP ${res.status}`);\n * return res.json();\n * });\n *\n * if (err) {\n * console.error('Failed to fetch users:', err.message);\n * return [];\n * }\n * return data;\n * ```\n */\nexport async function handle<T>(fn: () => Promise<T>): Promise<GoTuple<T, Error>> {\n try {\n const value = await fn();\n return [undefined, value];\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n return [err, undefined];\n }\n}\n\n/**\n * Wraps a synchronous function and returns a Go-style tuple.\n *\n * @param fn - The function to wrap\n * @returns A tuple of [error, value]\n *\n * @example\n * ```ts\n * const [err, parsed] = handleSync(() => JSON.parse(input));\n * if (err) {\n * console.error('Invalid JSON:', err.message);\n * return null;\n * }\n * return parsed;\n * ```\n */\nexport function handleSync<T>(fn: () => T): GoTuple<T, Error> {\n try {\n const value = fn();\n return [undefined, value];\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n return [err, undefined];\n }\n}\n"],"mappings":"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","names":["end"]}
|
|
1
|
+
{"version":3,"sources":["../../lib/markdown-utils.ts","../../lib/markdown-sanitize.ts","../../lib/result.ts"],"sourcesContent":["/**\n * Markdown Parsing Utilities\n *\n * Three implementations showing the tradeoffs between simple and more\n * defensive parsing:\n * - v1 (Naive): Direct call, crashes on bad input\n * - v2 (Defensive): Try-catch with null fallback\n * - v3 (Robust): Result type, validation, lightweight sanitization\n *\n * The default export points to the v3 implementation.\n */\n\nimport type { MDTree } from 'md4w';\nimport { mdToJSON } from 'md4w';\n\nimport { sanitizeMarkdownContent } from './markdown-sanitize';\nimport { err, ok, type Result } from './result';\n\n// -----------------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------------\n\n/**\n * Error codes for markdown parsing failures.\n */\nexport const ParseErrorCode = {\n INVALID_INPUT: 'INVALID_INPUT',\n EMPTY_CONTENT: 'EMPTY_CONTENT',\n PARSE_FAILED: 'PARSE_FAILED',\n CONTENT_TOO_LONG: 'CONTENT_TOO_LONG',\n NESTED_TOO_DEEP: 'NESTED_TOO_DEEP',\n} as const;\n\nexport type ParseErrorCode = (typeof ParseErrorCode)[keyof typeof ParseErrorCode];\n\n/**\n * Structured error for markdown parsing failures.\n */\nexport interface ParseError {\n code: ParseErrorCode;\n message: string;\n cause?: Error;\n}\n\n/**\n * Options for robust markdown parsing.\n */\nexport interface ParseOptions {\n /**\n * Maximum content length in characters.\n * @default 500000 (500KB of text, ~100K words)\n */\n maxLength?: number;\n\n /**\n * Whether to run a lightweight string-based sanitization pass before parsing.\n * @default true\n */\n sanitize?: boolean;\n\n /**\n * Whether to allow empty content.\n * @default false\n */\n allowEmpty?: boolean;\n}\n\n// -----------------------------------------------------------------------------\n// Constants\n// -----------------------------------------------------------------------------\n\nconst DEFAULT_MAX_LENGTH = 500_000; // 500KB of text\n\n/**\n * Patterns that indicate potential XSS attempts in markdown.\n * These are checked in raw content before parsing.\n */\nconst XSS_PATTERNS = [\n /<script\\b/i,\n /javascript:/i,\n /on\\w+\\s*=/i, // onclick=, onerror=, etc.\n /\\bdata:/i, // data: URIs can be dangerous\n /vbscript:/i,\n] as const;\n\n// -----------------------------------------------------------------------------\n// V1: Naive Implementation\n// -----------------------------------------------------------------------------\n\n/**\n * Naive markdown parser - crashes on bad input.\n *\n * DO NOT USE IN PRODUCTION. This is for demonstration only.\n *\n * Problems:\n * - No input validation\n * - No error handling\n * - Will crash the entire app on malformed input\n * - No protection against XSS\n * - No content limits\n *\n * @example\n * ```ts\n * // This crashes if content is null, undefined, or malformed\n * const ast = parseMarkdownV1(userInput);\n * ```\n */\nexport function parseMarkdownV1(content: string): MDTree {\n return mdToJSON(content);\n}\n\n// -----------------------------------------------------------------------------\n// V2: Defensive Implementation\n// -----------------------------------------------------------------------------\n\n/**\n * Defensive markdown parser - catches errors but loses context.\n *\n * Better than v1 but still problematic:\n * - Returns null on any error (loses error details)\n * - Caller can't distinguish between empty content and parse failure\n * - Still no XSS protection\n * - Still no content limits\n *\n * @example\n * ```ts\n * const ast = parseMarkdownV2(userInput);\n * if (!ast) {\n * // What went wrong? We don't know.\n * return <ErrorFallback />;\n * }\n * ```\n */\nexport function parseMarkdownV2(content: string): MDTree | null {\n try {\n // Basic null check\n if (content == null) {\n return null;\n }\n return mdToJSON(content);\n } catch {\n return null;\n }\n}\n\n// -----------------------------------------------------------------------------\n// V3: Robust Implementation\n// -----------------------------------------------------------------------------\n\n/**\n * Creates a ParseError with the given code and message.\n */\nfunction createParseError(code: ParseErrorCode, message: string, cause?: Error): ParseError {\n return { code, message, cause };\n}\n\n/**\n * Checks content for potential XSS patterns.\n * Returns the first matched pattern or null if clean.\n */\nfunction detectXssPattern(content: string): RegExp | null {\n for (const pattern of XSS_PATTERNS) {\n if (pattern.test(content)) {\n return pattern;\n }\n }\n return null;\n}\n\n/**\n * Sanitizes content by removing dangerous patterns.\n * This is a lightweight, string-based sanitizer and not a full HTML sanitizer.\n */\nfunction sanitizeContent(content: string): string {\n return sanitizeMarkdownContent(content);\n}\n\n/**\n * Robust markdown parser with explicit error handling and lightweight sanitization.\n *\n * Features:\n * - Input validation with specific error codes\n * - Result type for explicit error handling\n * - XSS pattern detection and optional lightweight sanitization\n * - Content length limits\n * - Preserves error context for debugging\n *\n * @param content - Markdown string to parse\n * @param options - Parsing options\n * @returns Result containing the AST or a structured error\n *\n * @example\n * ```ts\n * const result = parseMarkdownV3(userInput);\n *\n * if (!result.ok) {\n * switch (result.error.code) {\n * case 'INVALID_INPUT':\n * return <InvalidInputError message={result.error.message} />;\n * case 'CONTENT_TOO_LONG':\n * return <ContentTooLongError />;\n * case 'PARSE_FAILED':\n * console.error('Parse error:', result.error.cause);\n * return <ParseError />;\n * default:\n * return <GenericError />;\n * }\n * }\n *\n * return <MarkdownRenderer ast={result.value} />;\n * ```\n */\nexport function parseMarkdownV3(\n content: string,\n options: ParseOptions = {}\n): Result<MDTree, ParseError> {\n const { maxLength = DEFAULT_MAX_LENGTH, sanitize = true, allowEmpty = false } = options;\n\n // 1. Validate input type\n if (content === null || content === undefined) {\n return err(\n createParseError(\n ParseErrorCode.INVALID_INPUT,\n `Content must be a string, received ${content === null ? 'null' : 'undefined'}`\n )\n );\n }\n\n if (typeof content !== 'string') {\n return err(\n createParseError(\n ParseErrorCode.INVALID_INPUT,\n `Content must be a string, received ${typeof content}`\n )\n );\n }\n\n // 2. Handle empty content\n const trimmed = content.trim();\n if (trimmed.length === 0 && !allowEmpty) {\n return err(\n createParseError(ParseErrorCode.EMPTY_CONTENT, 'Content is empty or contains only whitespace')\n );\n }\n\n // 3. Check content length\n if (content.length > maxLength) {\n return err(\n createParseError(\n ParseErrorCode.CONTENT_TOO_LONG,\n `Content exceeds maximum length of ${maxLength.toLocaleString()} characters ` +\n `(received ${content.length.toLocaleString()})`\n )\n );\n }\n\n // 4. Check for XSS patterns\n const xssPattern = detectXssPattern(content);\n if (xssPattern) {\n if (process.env.NODE_ENV === 'development') {\n console.warn(\n `[parseMarkdownV3] XSS pattern detected: ${xssPattern.toString()}`,\n sanitize ? 'Content will be sanitized.' : 'Sanitization is disabled!'\n );\n }\n }\n\n // 5. Optionally sanitize content\n const processedContent = sanitize ? sanitizeContent(content) : content;\n\n // 6. Parse markdown with error handling\n try {\n const ast = mdToJSON(processedContent);\n return ok(ast);\n } catch (error) {\n const cause = error instanceof Error ? error : new Error(String(error));\n return err(\n createParseError(\n ParseErrorCode.PARSE_FAILED,\n `Failed to parse markdown: ${cause.message}`,\n cause\n )\n );\n }\n}\n\n// -----------------------------------------------------------------------------\n// Default Export\n// -----------------------------------------------------------------------------\n\n/**\n * Default markdown parser.\n *\n * This is an alias for `parseMarkdownV3`, the most defensive implementation\n * in this module.\n *\n * @example\n * ```ts\n * import { parseMarkdown } from '@/lib/markdown-utils';\n *\n * const result = parseMarkdown(content);\n * if (!result.ok) {\n * // Handle error with full context\n * console.error(`[${result.error.code}] ${result.error.message}`);\n * return null;\n * }\n * return result.value;\n * ```\n */\nexport const parseMarkdown = parseMarkdownV3;\n\n// -----------------------------------------------------------------------------\n// Utility Functions\n// -----------------------------------------------------------------------------\n\n/**\n * Checks if content is safe markdown (no XSS patterns detected).\n *\n * @param content - Markdown string to check\n * @returns true if no XSS patterns detected\n */\nexport function isSafeMarkdown(content: string): boolean {\n return detectXssPattern(content) === null;\n}\n\n/**\n * Estimates the word count of markdown content.\n * Useful for content length limits and reading time estimates.\n *\n * @param content - Markdown string to count\n * @returns Estimated word count\n */\nconst MAX_ESTIMATE_LENGTH = 500_000;\n\nfunction stripFencedCodeBlocks(text: string): string {\n let result = text;\n let start = result.indexOf('```');\n while (start !== -1) {\n const end = result.indexOf('```', start + 3);\n if (end === -1) break;\n result = result.slice(0, start) + result.slice(end + 3);\n start = result.indexOf('```');\n }\n return result;\n}\n\nfunction stripInlineCodeSpans(text: string): string {\n let result = text;\n let start = result.indexOf('`');\n while (start !== -1) {\n const end = result.indexOf('`', start + 1);\n if (end === -1) break;\n result = result.slice(0, start) + result.slice(end + 1);\n start = result.indexOf('`');\n }\n return result;\n}\n\nfunction stripMarkdownImages(text: string): string {\n let result = text;\n let index = result.indexOf('![');\n while (index !== -1) {\n const closeBracket = result.indexOf(']', index + 2);\n const openParen = closeBracket !== -1 ? result.indexOf('(', closeBracket) : -1;\n const closeParen = openParen !== -1 ? result.indexOf(')', openParen) : -1;\n if (closeBracket === -1 || openParen === -1 || closeParen === -1) break;\n result = result.slice(0, index) + result.slice(closeParen + 1);\n index = result.indexOf('![');\n }\n return result;\n}\n\nfunction convertMarkdownLinksToText(text: string): string {\n let result = text;\n let index = result.indexOf('[');\n while (index !== -1) {\n const closeBracket = result.indexOf(']', index + 1);\n const openParen = closeBracket !== -1 ? result.indexOf('(', closeBracket) : -1;\n const closeParen = openParen !== -1 ? result.indexOf(')', openParen) : -1;\n if (closeBracket === -1 || openParen === -1 || closeParen === -1) break;\n const label = result.slice(index + 1, closeBracket);\n result = result.slice(0, index) + label + result.slice(closeParen + 1);\n index = result.indexOf('[', index + label.length);\n }\n return result;\n}\n\nexport function estimateWordCount(content: string): number {\n const bounded =\n content.length > MAX_ESTIMATE_LENGTH ? content.slice(0, MAX_ESTIMATE_LENGTH) : content;\n\n let plainText = bounded;\n plainText = plainText.replace(/#+\\s/g, '');\n plainText = stripFencedCodeBlocks(plainText);\n plainText = stripInlineCodeSpans(plainText);\n plainText = stripMarkdownImages(plainText);\n plainText = convertMarkdownLinksToText(plainText);\n plainText = plainText.replace(/\\*\\*|__|~~/g, '');\n\n const words = plainText.trim().split(/\\s+/).filter(Boolean);\n return words.length;\n}\n\n/**\n * Estimates reading time for markdown content.\n *\n * @param content - Markdown string\n * @param wordsPerMinute - Reading speed (default 200 WPM)\n * @returns Reading time in minutes\n */\nexport function estimateReadingTime(content: string, wordsPerMinute = 200): number {\n const wordCount = estimateWordCount(content);\n return Math.ceil(wordCount / wordsPerMinute);\n}\n","/**\n * String-based markdown sanitization helpers (no regex backtracking on user input).\n */\n\nconst DANGEROUS_SCHEMES = ['javascript:', 'vbscript:', 'data:'] as const;\n\nconst HTML_URL_ATTRIBUTES = [\n 'href',\n 'src',\n 'action',\n 'formaction',\n 'cite',\n 'background',\n 'poster',\n 'xlink:href',\n] as const;\n\nfunction stripMarkdownFormattingMarkers(value: string): string {\n return value\n .replaceAll('`', '')\n .replaceAll('**', '')\n .replaceAll('__', '')\n .replaceAll('~~', '')\n .replaceAll('*', '')\n .replaceAll('_', '');\n}\n\nfunction isHtmlWhitespace(ch: string): boolean {\n return ch === ' ' || ch === '\\t' || ch === '\\n' || ch === '\\r' || ch === '\\f' || ch === '\\v';\n}\n\nfunction findUnescaped(content: string, char: string, from: number): number {\n let index = from;\n\n while (index < content.length) {\n const found = content.indexOf(char, index);\n if (found === -1) return -1;\n\n let backslashCount = 0;\n for (let i = found - 1; i >= 0 && content.charAt(i) === '\\\\'; i -= 1) {\n backslashCount += 1;\n }\n\n if (backslashCount % 2 === 0) return found;\n index = found + 1;\n }\n\n return -1;\n}\n\nexport function stripMarkdownFormattingFromImageAltText(content: string): string {\n let result = content;\n let index = 0;\n\n while (index < result.length) {\n const imageStart = result.indexOf('![', index);\n if (imageStart === -1) break;\n\n const altStart = imageStart + 2;\n const altEnd = findUnescaped(result, ']', altStart);\n if (altEnd === -1) {\n index = altStart;\n continue;\n }\n\n if (result.charAt(altEnd + 1) !== '(') {\n index = altEnd + 1;\n continue;\n }\n\n const altText = result.slice(altStart, altEnd);\n const plainAltText = stripMarkdownFormattingMarkers(altText);\n if (plainAltText === altText) {\n index = altEnd + 2;\n continue;\n }\n\n result = result.slice(0, altStart) + plainAltText + result.slice(altEnd);\n index = altStart + plainAltText.length + 2;\n }\n\n return result;\n}\n\nfunction neutralizeDangerousSchemesInUrl(url: string): string {\n let result = url;\n let lower = result.toLowerCase();\n\n for (const scheme of DANGEROUS_SCHEMES) {\n const lowerScheme = scheme.toLowerCase();\n let index = lower.indexOf(lowerScheme);\n\n while (index !== -1) {\n const atUrlStart = index === 0;\n const afterSeparator =\n index > 0 &&\n (isHtmlWhitespace(result.charAt(index - 1)) || result.charAt(index - 1) === '\"');\n\n if (atUrlStart || afterSeparator) {\n result = `${result.slice(0, index)}removed:${result.slice(index + scheme.length)}`;\n lower = result.toLowerCase();\n index = lower.indexOf(lowerScheme, index + 'removed:'.length);\n } else {\n index = lower.indexOf(lowerScheme, index + 1);\n }\n }\n }\n\n return result;\n}\n\nexport function stripScriptTags(content: string): string {\n let result = content;\n let lower = result.toLowerCase();\n let searchFrom = 0;\n\n while (searchFrom < lower.length) {\n const start = lower.indexOf('<script', searchFrom);\n if (start === -1) break;\n\n const closeStart = lower.indexOf('</script', start);\n if (closeStart === -1) {\n // No closing tag — remove the opening <script...> up to the next >\n let end = lower.indexOf('>', start);\n if (end === -1) {\n end = lower.length;\n } else {\n end += 1;\n }\n result = result.slice(0, start) + result.slice(end);\n lower = result.toLowerCase();\n searchFrom = start;\n continue;\n }\n\n let end = lower.indexOf('>', closeStart);\n if (end === -1) {\n searchFrom = start + 1;\n continue;\n }\n end += 1;\n\n result = result.slice(0, start) + result.slice(end);\n lower = result.toLowerCase();\n searchFrom = start;\n }\n\n return result;\n}\n\nfunction replaceUrlInParenGroup(\n content: string,\n openParen: number,\n closeParen: number\n): { content: string; nextIndex: number } {\n const url = content.slice(openParen + 1, closeParen);\n const sanitized = neutralizeDangerousSchemesInUrl(url);\n if (sanitized === url) {\n return { content, nextIndex: closeParen + 1 };\n }\n return {\n content: content.slice(0, openParen + 1) + sanitized + content.slice(closeParen),\n nextIndex: openParen + sanitized.length + 1,\n };\n}\n\nfunction neutralizeMarkdownLinkAndImageDestinations(content: string): string {\n let result = content;\n let index = 0;\n\n while (index < result.length) {\n const isImage = result.startsWith('![', index);\n const marker = isImage ? '![' : '[';\n const markerIndex = isImage ? index : result.indexOf('[', index);\n\n if (markerIndex === -1) break;\n if (!isImage && result.startsWith('![', markerIndex)) {\n index = markerIndex + 2;\n continue;\n }\n\n const bracketStart = isImage ? markerIndex + 2 : markerIndex + 1;\n const closeBracket = result.indexOf(']', bracketStart);\n if (closeBracket === -1) {\n index = markerIndex + marker.length;\n continue;\n }\n\n const openParen = result.indexOf('(', closeBracket);\n if (openParen !== closeBracket + 1) {\n index = markerIndex + marker.length;\n continue;\n }\n\n const closeParen = result.indexOf(')', openParen);\n if (closeParen === -1) {\n // Malformed link with no closing paren — still neutralize dangerous schemes\n let urlEnd = openParen + 1;\n while (\n urlEnd < result.length &&\n !isHtmlWhitespace(result.charAt(urlEnd)) &&\n result.charAt(urlEnd) !== '\\n' &&\n result.charAt(urlEnd) !== '\\r'\n ) {\n urlEnd += 1;\n }\n const url = result.slice(openParen + 1, urlEnd);\n const sanitized = neutralizeDangerousSchemesInUrl(url);\n if (sanitized !== url) {\n result = result.slice(0, openParen + 1) + sanitized + result.slice(urlEnd);\n }\n index = openParen + sanitized.length + 1;\n continue;\n }\n\n const replaced = replaceUrlInParenGroup(result, openParen, closeParen);\n result = replaced.content;\n index = replaced.nextIndex;\n }\n\n return result;\n}\n\nfunction readHtmlAttributeValue(content: string, valueStart: number): number {\n let end = valueStart;\n while (end < content.length && isHtmlWhitespace(content.charAt(end))) {\n end += 1;\n }\n\n const quote = content.charAt(end);\n if (quote === '\"' || quote === \"'\") {\n end += 1;\n while (end < content.length && content.charAt(end) !== quote) {\n end += 1;\n }\n if (end < content.length) end += 1;\n return end;\n }\n\n while (end < content.length) {\n const ch = content.charAt(end);\n if (isHtmlWhitespace(ch) || ch === '>') break;\n end += 1;\n }\n return end;\n}\n\nfunction findHtmlAttribute(\n _content: string,\n lower: string,\n attrName: string,\n from: number\n): number {\n const needle = attrName.toLowerCase();\n let i = from;\n\n while (i < lower.length) {\n const idx = lower.indexOf(needle, i);\n if (idx === -1) return -1;\n\n const before = idx > 0 ? lower.charAt(idx - 1) : '';\n if (idx > 0 && !isHtmlWhitespace(before) && before !== '<' && before !== '/') {\n i = idx + 1;\n continue;\n }\n\n let after = idx + needle.length;\n while (after < lower.length && isHtmlWhitespace(lower.charAt(after))) {\n after += 1;\n }\n\n if (lower.charAt(after) === '=') return idx;\n i = idx + 1;\n }\n\n return -1;\n}\n\nfunction neutralizeHtmlUrlAttributes(content: string): string {\n let result = content;\n let lower = result.toLowerCase();\n\n for (const attr of HTML_URL_ATTRIBUTES) {\n let searchFrom = 0;\n\n while (searchFrom < lower.length) {\n const attrIndex = findHtmlAttribute(result, lower, attr, searchFrom);\n if (attrIndex === -1) break;\n\n let valueStart = attrIndex + attr.length;\n while (valueStart < lower.length && isHtmlWhitespace(lower.charAt(valueStart))) {\n valueStart += 1;\n }\n if (lower.charAt(valueStart) !== '=') {\n searchFrom = attrIndex + 1;\n continue;\n }\n valueStart += 1;\n while (valueStart < result.length && isHtmlWhitespace(result.charAt(valueStart))) {\n valueStart += 1;\n }\n\n const valueEnd = readHtmlAttributeValue(result, valueStart);\n const rawValue = result.slice(valueStart, valueEnd);\n const quote =\n rawValue.charAt(0) === '\"' || rawValue.charAt(0) === \"'\" ? rawValue.charAt(0) : null;\n const innerStart = quote ? 1 : 0;\n const innerEnd =\n quote && rawValue.charAt(rawValue.length - 1) === quote\n ? rawValue.length - 1\n : rawValue.length;\n const url = rawValue.slice(innerStart, innerEnd);\n const sanitized = neutralizeDangerousSchemesInUrl(url);\n const wrapped = quote ? `${quote}${sanitized}${quote}` : sanitized;\n\n if (sanitized !== url) {\n result = result.slice(0, valueStart) + wrapped + result.slice(valueEnd);\n lower = result.toLowerCase();\n }\n\n searchFrom = valueStart + (sanitized !== url ? wrapped.length : rawValue.length);\n }\n }\n\n return result;\n}\n\nfunction stripEventHandlersFromTag(tag: string): string {\n let result = tag;\n let lower = result.toLowerCase();\n let searchFrom = 0;\n\n while (searchFrom < lower.length) {\n let eventStart = -1;\n\n for (let j = searchFrom; j < lower.length; j++) {\n if (lower.charAt(j) !== 'o' || !lower.startsWith('on', j)) continue;\n\n if (j > 0 && !isHtmlWhitespace(lower.charAt(j - 1))) continue;\n\n let nameEnd = j + 2;\n while (nameEnd < lower.length) {\n const ch = lower.charAt(nameEnd);\n if (!((ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch === '-')) break;\n nameEnd += 1;\n }\n\n let eqIndex = nameEnd;\n while (eqIndex < lower.length && isHtmlWhitespace(lower.charAt(eqIndex))) {\n eqIndex += 1;\n }\n\n if (lower.charAt(eqIndex) !== '=' || nameEnd - j < 3) continue;\n\n eventStart = j;\n break;\n }\n\n if (eventStart === -1) break;\n\n const valueEnd = readHtmlAttributeValue(result, lower.indexOf('=', eventStart) + 1);\n result = result.slice(0, eventStart) + result.slice(valueEnd);\n lower = result.toLowerCase();\n searchFrom = eventStart;\n }\n\n return result;\n}\n\nexport function stripInlineEventHandlers(content: string): string {\n let result = content;\n let i = 0;\n\n while (i < result.length) {\n const tagStart = result.indexOf('<', i);\n if (tagStart === -1) break;\n\n if (result.startsWith('<!--', tagStart)) {\n const commentEnd = result.indexOf('-->', tagStart);\n i = commentEnd === -1 ? result.length : commentEnd + 3;\n continue;\n }\n\n const tagEnd = result.indexOf('>', tagStart);\n if (tagEnd === -1) break;\n\n const tag = result.slice(tagStart, tagEnd + 1);\n const sanitizedTag = stripEventHandlersFromTag(tag);\n result = result.slice(0, tagStart) + sanitizedTag + result.slice(tagEnd + 1);\n i = tagStart + sanitizedTag.length;\n }\n\n return result;\n}\n\nexport function sanitizeMarkdownContent(content: string): string {\n let sanitized = stripMarkdownFormattingFromImageAltText(content);\n sanitized = stripScriptTags(sanitized);\n sanitized = neutralizeMarkdownLinkAndImageDestinations(sanitized);\n sanitized = neutralizeHtmlUrlAttributes(sanitized);\n return stripInlineEventHandlers(sanitized);\n}\n\nexport function containsDangerousScheme(content: string, scheme: string): boolean {\n return content.toLowerCase().includes(scheme.toLowerCase());\n}\n","/**\n * Result Type Utilities\n *\n * Go-style error handling with discriminated union types.\n * Provides type-safe success/error handling without exceptions.\n *\n * @example\n * ```ts\n * const [err, data] = await handle(async () => {\n * const response = await fetch(url);\n * if (!response.ok) throw new Error(`HTTP ${response.status}`);\n * return response.json();\n * });\n *\n * if (err) {\n * console.error('Failed:', err.message);\n * return { error: err.message };\n * }\n * return { data };\n * ```\n */\n\n// -----------------------------------------------------------------------------\n// Result Type\n// -----------------------------------------------------------------------------\n\n/**\n * A discriminated union representing either success or failure.\n *\n * @template T - The success value type\n * @template E - The error type (defaults to Error)\n *\n * @example\n * ```ts\n * function divide(a: number, b: number): Result<number, string> {\n * if (b === 0) return err('Division by zero');\n * return ok(a / b);\n * }\n *\n * const result = divide(10, 2);\n * if (isOk(result)) {\n * console.log('Result:', result.value); // 5\n * } else {\n * console.error('Error:', result.error);\n * }\n * ```\n */\nexport type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };\n\n// -----------------------------------------------------------------------------\n// Constructors\n// -----------------------------------------------------------------------------\n\n/**\n * Creates a success Result.\n *\n * @param value - The success value\n * @returns A success Result containing the value\n *\n * @example\n * ```ts\n * const result = ok(42);\n * // { ok: true, value: 42 }\n * ```\n */\nexport function ok<T>(value: T): Result<T, never> {\n return { ok: true, value };\n}\n\n/**\n * Creates a failure Result.\n *\n * @param error - The error value\n * @returns A failure Result containing the error\n *\n * @example\n * ```ts\n * const result = err(new Error('Something went wrong'));\n * // { ok: false, error: Error('Something went wrong') }\n * ```\n */\nexport function err<E>(error: E): Result<never, E> {\n return { ok: false, error };\n}\n\n// -----------------------------------------------------------------------------\n// Type Guards\n// -----------------------------------------------------------------------------\n\n/**\n * Type guard to check if a Result is successful.\n *\n * @param result - The Result to check\n * @returns true if the Result is a success\n *\n * @example\n * ```ts\n * const result = fetchData();\n * if (isOk(result)) {\n * // TypeScript knows result.value exists here\n * console.log(result.value);\n * }\n * ```\n */\nexport function isOk<T, E>(result: Result<T, E>): result is { ok: true; value: T } {\n return result.ok === true;\n}\n\n/**\n * Type guard to check if a Result is a failure.\n *\n * @param result - The Result to check\n * @returns true if the Result is a failure\n *\n * @example\n * ```ts\n * const result = fetchData();\n * if (isErr(result)) {\n * // TypeScript knows result.error exists here\n * console.error(result.error);\n * }\n * ```\n */\nexport function isErr<T, E>(result: Result<T, E>): result is { ok: false; error: E } {\n return result.ok === false;\n}\n\n// -----------------------------------------------------------------------------\n// Extractors\n// -----------------------------------------------------------------------------\n\n/**\n * Extracts the value from a Result, throwing if it's an error.\n *\n * @param result - The Result to unwrap\n * @returns The success value\n * @throws The error if Result is a failure\n *\n * @example\n * ```ts\n * const result = ok(42);\n * const value = unwrap(result); // 42\n *\n * const errorResult = err(new Error('fail'));\n * const value2 = unwrap(errorResult); // throws Error('fail')\n * ```\n */\nexport function unwrap<T, E>(result: Result<T, E>): T {\n if (isOk(result)) {\n return result.value;\n }\n throw result.error;\n}\n\n/**\n * Extracts the value from a Result, returning a default on error.\n *\n * @param result - The Result to unwrap\n * @param defaultValue - The value to return if Result is an error\n * @returns The success value or the default value\n *\n * @example\n * ```ts\n * const result = err(new Error('fail'));\n * const value = unwrapOr(result, 0); // 0\n *\n * const okResult = ok(42);\n * const value2 = unwrapOr(okResult, 0); // 42\n * ```\n */\nexport function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T {\n if (isOk(result)) {\n return result.value;\n }\n return defaultValue;\n}\n\n/**\n * Extracts the value from a Result, computing a default on error.\n *\n * @param result - The Result to unwrap\n * @param fn - Function to compute the default value from the error\n * @returns The success value or the computed default\n *\n * @example\n * ```ts\n * const result = err(new Error('not found'));\n * const value = unwrapOrElse(result, (e) => {\n * console.error('Error:', e.message);\n * return [];\n * });\n * ```\n */\nexport function unwrapOrElse<T, E>(result: Result<T, E>, fn: (error: E) => T): T {\n if (isOk(result)) {\n return result.value;\n }\n return fn(result.error);\n}\n\n// -----------------------------------------------------------------------------\n// Transformers\n// -----------------------------------------------------------------------------\n\n/**\n * Maps a successful Result's value.\n *\n * @param result - The Result to map\n * @param fn - Function to transform the value\n * @returns A new Result with the transformed value, or the original error\n *\n * @example\n * ```ts\n * const result = ok(5);\n * const doubled = map(result, (n) => n * 2); // ok(10)\n *\n * const errorResult = err('fail');\n * const still = map(errorResult, (n) => n * 2); // err('fail')\n * ```\n */\nexport function map<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E> {\n if (isOk(result)) {\n return ok(fn(result.value));\n }\n return result;\n}\n\n/**\n * Maps a failed Result's error.\n *\n * @param result - The Result to map\n * @param fn - Function to transform the error\n * @returns A new Result with the transformed error, or the original value\n *\n * @example\n * ```ts\n * const result = err('not found');\n * const mapped = mapErr(result, (e) => new Error(e)); // err(Error('not found'))\n * ```\n */\nexport function mapErr<T, E, F>(result: Result<T, E>, fn: (error: E) => F): Result<T, F> {\n if (isErr(result)) {\n return err(fn(result.error));\n }\n return result;\n}\n\n/**\n * Chains Result-returning operations.\n *\n * @param result - The Result to chain from\n * @param fn - Function that returns a new Result\n * @returns The chained Result\n *\n * @example\n * ```ts\n * function parse(input: string): Result<number, string> {\n * const n = parseInt(input, 10);\n * return isNaN(n) ? err('not a number') : ok(n);\n * }\n *\n * function double(n: number): Result<number, string> {\n * return ok(n * 2);\n * }\n *\n * const result = flatMap(parse('5'), double); // ok(10)\n * const fail = flatMap(parse('abc'), double); // err('not a number')\n * ```\n */\nexport function flatMap<T, U, E>(\n result: Result<T, E>,\n fn: (value: T) => Result<U, E>\n): Result<U, E> {\n if (isOk(result)) {\n return fn(result.value);\n }\n return result;\n}\n\n// -----------------------------------------------------------------------------\n// Async Helpers\n// -----------------------------------------------------------------------------\n\n/**\n * Wraps a Promise in a Result type.\n *\n * @param promise - The Promise to wrap\n * @returns A Promise that resolves to a Result\n *\n * @example\n * ```ts\n * const result = await fromPromise(fetch('/api/data'));\n * if (isErr(result)) {\n * console.error('Fetch failed:', result.error);\n * return;\n * }\n * const response = result.value;\n * ```\n */\nexport async function fromPromise<T>(promise: Promise<T>): Promise<Result<T, Error>> {\n try {\n const value = await promise;\n return ok(value);\n } catch (error) {\n return err(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\n/**\n * Wraps a throwing function in a Result type.\n *\n * @param fn - The function to wrap\n * @returns A Result containing the return value or the thrown error\n *\n * @example\n * ```ts\n * const result = tryCatch(() => JSON.parse(input));\n * if (isErr(result)) {\n * console.error('Invalid JSON:', result.error);\n * return null;\n * }\n * return result.value;\n * ```\n */\nexport function tryCatch<T>(fn: () => T): Result<T, Error> {\n try {\n return ok(fn());\n } catch (error) {\n return err(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\n/**\n * Wraps an async function in a Result type.\n *\n * @param fn - The async function to wrap\n * @returns A Promise that resolves to a Result\n *\n * @example\n * ```ts\n * const result = await tryCatchAsync(async () => {\n * const response = await fetch('/api/data');\n * if (!response.ok) throw new Error(`HTTP ${response.status}`);\n * return response.json();\n * });\n * ```\n */\nexport async function tryCatchAsync<T>(fn: () => Promise<T>): Promise<Result<T, Error>> {\n try {\n const value = await fn();\n return ok(value);\n } catch (error) {\n return err(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\n// -----------------------------------------------------------------------------\n// Tuple Helpers (Go-style)\n// -----------------------------------------------------------------------------\n\n/**\n * Go-style tuple for error handling: [error, value]\n *\n * @example\n * ```ts\n * const [err, data] = await handle(fetchData);\n * if (err) {\n * console.error(err);\n * return;\n * }\n * console.log(data);\n * ```\n */\nexport type GoTuple<T, E = Error> = [E, undefined] | [undefined, T];\n\n/**\n * Converts a Result to a Go-style tuple.\n *\n * @param result - The Result to convert\n * @returns A tuple of [error, value]\n *\n * @example\n * ```ts\n * const result = await fetchData();\n * const [err, data] = toTuple(result);\n * ```\n */\nexport function toTuple<T, E>(result: Result<T, E>): GoTuple<T, E> {\n if (isOk(result)) {\n return [undefined, result.value];\n }\n return [result.error, undefined];\n}\n\n/**\n * Wraps an async function and returns a Go-style tuple.\n *\n * @param fn - The async function to wrap\n * @returns A Promise that resolves to [error, value] tuple\n *\n * @example\n * ```ts\n * const [err, data] = await handle(async () => {\n * const res = await fetch('/api/users');\n * if (!res.ok) throw new Error(`HTTP ${res.status}`);\n * return res.json();\n * });\n *\n * if (err) {\n * console.error('Failed to fetch users:', err.message);\n * return [];\n * }\n * return data;\n * ```\n */\nexport async function handle<T>(fn: () => Promise<T>): Promise<GoTuple<T, Error>> {\n try {\n const value = await fn();\n return [undefined, value];\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n return [err, undefined];\n }\n}\n\n/**\n * Wraps a synchronous function and returns a Go-style tuple.\n *\n * @param fn - The function to wrap\n * @returns A tuple of [error, value]\n *\n * @example\n * ```ts\n * const [err, parsed] = handleSync(() => JSON.parse(input));\n * if (err) {\n * console.error('Invalid JSON:', err.message);\n * return null;\n * }\n * return parsed;\n * ```\n */\nexport function handleSync<T>(fn: () => T): GoTuple<T, Error> {\n try {\n const value = fn();\n return [undefined, value];\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n return [err, undefined];\n }\n}\n"],"mappings":"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","names":["end"]}
|