@stream-mdx/core 0.0.0 → 0.0.2
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/CHANGELOG.md +13 -0
- package/README.md +26 -37
- package/dist/code-highlighting.cjs +0 -1
- package/dist/code-highlighting.mjs +0 -1
- package/dist/index.cjs +94 -2
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +93 -2
- package/dist/inline-parser.cjs +0 -1
- package/dist/inline-parser.mjs +0 -1
- package/dist/mixed-content.cjs +0 -1
- package/dist/mixed-content.mjs +0 -1
- package/dist/perf/backpressure.cjs +0 -1
- package/dist/perf/backpressure.mjs +0 -1
- package/dist/perf/patch-batching.cjs +0 -1
- package/dist/perf/patch-batching.mjs +0 -1
- package/dist/perf/patch-coalescing.cjs +0 -1
- package/dist/perf/patch-coalescing.mjs +0 -1
- package/dist/security.cjs +1 -2
- package/dist/security.d.cts +1 -1
- package/dist/security.d.ts +1 -1
- package/dist/security.mjs +1 -2
- package/dist/streaming/custom-matcher.cjs +118 -0
- package/dist/streaming/custom-matcher.d.cts +22 -0
- package/dist/streaming/custom-matcher.d.ts +22 -0
- package/dist/streaming/custom-matcher.mjs +93 -0
- package/dist/types.cjs +0 -1
- package/dist/types.mjs +0 -1
- package/dist/utils.cjs +0 -1
- package/dist/utils.mjs +0 -1
- package/dist/worker-html-sanitizer.cjs +0 -1
- package/dist/worker-html-sanitizer.mjs +0 -1
- package/package.json +12 -2
- package/dist/code-highlighting.cjs.map +0 -1
- package/dist/code-highlighting.mjs.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/inline-parser.cjs.map +0 -1
- package/dist/inline-parser.mjs.map +0 -1
- package/dist/mixed-content.cjs.map +0 -1
- package/dist/mixed-content.mjs.map +0 -1
- package/dist/perf/backpressure.cjs.map +0 -1
- package/dist/perf/backpressure.mjs.map +0 -1
- package/dist/perf/patch-batching.cjs.map +0 -1
- package/dist/perf/patch-batching.mjs.map +0 -1
- package/dist/perf/patch-coalescing.cjs.map +0 -1
- package/dist/perf/patch-coalescing.mjs.map +0 -1
- package/dist/security.cjs.map +0 -1
- package/dist/security.mjs.map +0 -1
- package/dist/types.cjs.map +0 -1
- package/dist/types.mjs.map +0 -1
- package/dist/utils.cjs.map +0 -1
- package/dist/utils.mjs.map +0 -1
- package/dist/worker-html-sanitizer.cjs.map +0 -1
- package/dist/worker-html-sanitizer.mjs.map +0 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// src/streaming/custom-matcher.ts
|
|
2
|
+
var CustomStreamingMatcher = class {
|
|
3
|
+
constructor(pattern) {
|
|
4
|
+
this.buffer = "";
|
|
5
|
+
this.possibleMatches = [];
|
|
6
|
+
this.pattern = pattern;
|
|
7
|
+
}
|
|
8
|
+
addCharacter(char) {
|
|
9
|
+
this.buffer += char;
|
|
10
|
+
const fullMatch = this.buffer.match(this.pattern);
|
|
11
|
+
if (fullMatch && fullMatch.index === 0) {
|
|
12
|
+
const match = {
|
|
13
|
+
matched: true,
|
|
14
|
+
content: fullMatch[0],
|
|
15
|
+
length: fullMatch[0].length,
|
|
16
|
+
isComplete: true
|
|
17
|
+
};
|
|
18
|
+
this.buffer = this.buffer.slice(fullMatch[0].length);
|
|
19
|
+
this.possibleMatches = [];
|
|
20
|
+
return match;
|
|
21
|
+
}
|
|
22
|
+
const confidence = this.calculatePartialConfidence();
|
|
23
|
+
return {
|
|
24
|
+
matched: false,
|
|
25
|
+
content: this.buffer,
|
|
26
|
+
length: this.buffer.length,
|
|
27
|
+
isComplete: false,
|
|
28
|
+
confidence
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
addString(str) {
|
|
32
|
+
const results = [];
|
|
33
|
+
for (const char of str) {
|
|
34
|
+
const result = this.addCharacter(char);
|
|
35
|
+
if (result.matched) {
|
|
36
|
+
results.push(result);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (results.length === 0 && this.buffer.length > 0) {
|
|
40
|
+
results.push({
|
|
41
|
+
matched: false,
|
|
42
|
+
content: this.buffer,
|
|
43
|
+
length: this.buffer.length,
|
|
44
|
+
isComplete: false,
|
|
45
|
+
confidence: this.calculatePartialConfidence()
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return results;
|
|
49
|
+
}
|
|
50
|
+
couldMatch() {
|
|
51
|
+
if (this.buffer.length === 0) return true;
|
|
52
|
+
const patternSource = this.pattern.source;
|
|
53
|
+
const flags = this.pattern.flags;
|
|
54
|
+
try {
|
|
55
|
+
const partialPattern = new RegExp(`^${patternSource.replace(/\\$$/, "")}`, flags);
|
|
56
|
+
const testString = this.buffer + "X".repeat(100);
|
|
57
|
+
return partialPattern.test(testString);
|
|
58
|
+
} catch {
|
|
59
|
+
return this.isValidPrefix();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
reset() {
|
|
63
|
+
this.buffer = "";
|
|
64
|
+
this.possibleMatches = [];
|
|
65
|
+
}
|
|
66
|
+
getBuffer() {
|
|
67
|
+
return this.buffer;
|
|
68
|
+
}
|
|
69
|
+
calculatePartialConfidence() {
|
|
70
|
+
if (this.buffer.length === 0) return 0;
|
|
71
|
+
let confidence = 0.1;
|
|
72
|
+
confidence += Math.min(0.4, this.buffer.length * 0.1);
|
|
73
|
+
if (this.couldMatch()) {
|
|
74
|
+
confidence += 0.3;
|
|
75
|
+
}
|
|
76
|
+
if (this.buffer.startsWith("$")) confidence += 0.2;
|
|
77
|
+
if (this.buffer.startsWith("$$")) confidence += 0.3;
|
|
78
|
+
return Math.min(1, confidence);
|
|
79
|
+
}
|
|
80
|
+
isValidPrefix() {
|
|
81
|
+
const patternStr = this.pattern.source;
|
|
82
|
+
if (patternStr.includes("\\$\\$") && (this.buffer === "$" || this.buffer === "$$")) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
if (patternStr.includes("\\$") && this.buffer === "$") {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
export {
|
|
92
|
+
CustomStreamingMatcher
|
|
93
|
+
};
|
package/dist/types.cjs
CHANGED
package/dist/types.mjs
CHANGED
package/dist/utils.cjs
CHANGED
package/dist/utils.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream-mdx/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Core types, snapshot utilities, and perf helpers for the Streaming Markdown V2 stack",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -70,9 +70,18 @@
|
|
|
70
70
|
"types": "./dist/security.d.ts",
|
|
71
71
|
"import": "./dist/security.mjs",
|
|
72
72
|
"require": "./dist/security.cjs"
|
|
73
|
+
},
|
|
74
|
+
"./streaming/custom-matcher": {
|
|
75
|
+
"types": "./dist/streaming/custom-matcher.d.ts",
|
|
76
|
+
"import": "./dist/streaming/custom-matcher.mjs",
|
|
77
|
+
"require": "./dist/streaming/custom-matcher.cjs"
|
|
73
78
|
}
|
|
74
79
|
},
|
|
75
|
-
"files": [
|
|
80
|
+
"files": [
|
|
81
|
+
"dist",
|
|
82
|
+
"README.md",
|
|
83
|
+
"CHANGELOG.md"
|
|
84
|
+
],
|
|
76
85
|
"sideEffects": false,
|
|
77
86
|
"scripts": {
|
|
78
87
|
"build": "tsup",
|
|
@@ -81,6 +90,7 @@
|
|
|
81
90
|
"prepack": "npm run build"
|
|
82
91
|
},
|
|
83
92
|
"dependencies": {
|
|
93
|
+
"@lezer/common": "^1.2.3",
|
|
84
94
|
"@lezer/markdown": "^1.3.0",
|
|
85
95
|
"dompurify": "^3.1.6",
|
|
86
96
|
"rehype-parse": "^9.0.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/code-highlighting.ts"],"sourcesContent":["export type HighlightedLine = string | null;\n\nfunction normalizeNewlines(input: string): string {\n return input.replace(/\\r\\n?/g, \"\\n\");\n}\n\nfunction isFenceLine(line: string): boolean {\n return /^```/.test(line.trim());\n}\n\nexport function stripCodeFence(raw: string): { code: string; info: string; hadFence: boolean } {\n if (!raw) {\n return { code: \"\", info: \"\", hadFence: false };\n }\n const normalized = normalizeNewlines(raw);\n const lines = normalized.split(\"\\n\");\n if (lines.length === 0) {\n return { code: normalized, info: \"\", hadFence: false };\n }\n\n const firstLine = lines[0];\n if (!isFenceLine(firstLine)) {\n return { code: normalized, info: \"\", hadFence: false };\n }\n\n const info = firstLine.slice(3).trim();\n let endIndex = lines.length - 1;\n while (endIndex > 0 && lines[endIndex].trim().length === 0) {\n endIndex--;\n }\n if (endIndex > 0 && isFenceLine(lines[endIndex])) {\n const codeLines = lines.slice(1, endIndex);\n return { code: codeLines.join(\"\\n\"), info, hadFence: true };\n }\n\n // During streaming, code block may not have closing fence yet\n // Still extract and return the info from the opening fence\n const codeLines = lines.slice(1);\n return { code: codeLines.join(\"\\n\"), info, hadFence: true };\n}\n\nfunction getDomParser(): DOMParser | null {\n if (typeof window !== \"undefined\" && typeof window.DOMParser === \"function\") {\n return new window.DOMParser();\n }\n if (typeof DOMParser !== \"undefined\") {\n try {\n return new DOMParser();\n } catch (e) {\n return null;\n }\n }\n return null;\n}\n\nexport function extractHighlightedLines(html: string, fallbackLength: number): HighlightedLine[] {\n if (!html) {\n return new Array(Math.max(0, fallbackLength)).fill(null);\n }\n\n const parser = getDomParser();\n if (parser) {\n try {\n const doc = parser.parseFromString(`<div>${html}</div>`, \"text/html\");\n const nodes = doc.querySelectorAll(\"span.line\");\n if (nodes.length > 0) {\n const lines: HighlightedLine[] = [];\n for (const node of nodes) {\n lines.push(node instanceof Element ? node.innerHTML : null);\n }\n return normalizeHighlightedLines(lines, fallbackLength);\n }\n } catch (error) {\n // fall through to manual extraction\n }\n }\n\n return manualExtractHighlightedLines(html, fallbackLength);\n}\n\nfunction normalizeHighlightedLines(lines: HighlightedLine[], fallbackLength: number): HighlightedLine[] {\n if (!lines || lines.length === 0) {\n return new Array(Math.max(0, fallbackLength)).fill(null);\n }\n const length = Math.max(fallbackLength, lines.length);\n const result: HighlightedLine[] = new Array(length).fill(null);\n for (let i = 0; i < lines.length; i++) {\n result[i] = lines[i];\n }\n return result;\n}\n\nfunction manualExtractHighlightedLines(html: string, fallbackLength: number): HighlightedLine[] {\n const lineRegex = /<span class=\"line\"(?:\\s+[^>]*)?>/gi;\n const lines: string[] = [];\n let match: RegExpExecArray | null = lineRegex.exec(html);\n\n while (match !== null) {\n const openTagEnd = html.indexOf(\">\", match.index);\n if (openTagEnd === -1) break;\n let cursor = openTagEnd + 1;\n let depth = 1;\n let buffer = \"\";\n\n while (cursor < html.length && depth > 0) {\n const nextTagStart = html.indexOf(\"<\", cursor);\n if (nextTagStart === -1) {\n buffer += html.slice(cursor);\n cursor = html.length;\n break;\n }\n if (nextTagStart > cursor) {\n buffer += html.slice(cursor, nextTagStart);\n }\n cursor = nextTagStart;\n if (html.startsWith(\"</span>\", cursor)) {\n depth -= 1;\n cursor += 7;\n if (depth === 0) break;\n buffer += \"</span>\";\n continue;\n }\n const tagEnd = html.indexOf(\">\", cursor);\n if (tagEnd === -1) {\n buffer += html.slice(cursor);\n cursor = html.length;\n break;\n }\n const tag = html.slice(cursor, tagEnd + 1);\n if (/^<span\\b/i.test(tag)) {\n depth += 1;\n }\n buffer += tag;\n cursor = tagEnd + 1;\n }\n\n lines.push(buffer);\n lineRegex.lastIndex = cursor;\n match = lineRegex.exec(html);\n }\n\n return normalizeHighlightedLines(lines, fallbackLength);\n}\n\nexport function dedentIndentedCode(raw: string): string {\n if (!raw) return \"\";\n const normalized = normalizeNewlines(raw);\n const lines = normalized.split(\"\\n\");\n let minIndent = Number.POSITIVE_INFINITY;\n for (const line of lines) {\n const match = line.match(/^\\s+/);\n if (!match) continue;\n minIndent = Math.min(minIndent, match[0].length);\n }\n if (!Number.isFinite(minIndent) || minIndent === 0) {\n return normalized;\n }\n return lines.map((line) => (line.startsWith(\" \".repeat(minIndent)) ? line.slice(minIndent) : line)).join(\"\\n\");\n}\n\nexport function extractCodeLines(raw: string): string[] {\n if (!raw) return [];\n const normalized = normalizeNewlines(raw);\n const { code, hadFence } = stripCodeFence(normalized);\n if (hadFence) {\n return code.split(\"\\n\");\n }\n if (/^\\s{4}/m.test(normalized)) {\n return dedentIndentedCode(normalized).split(\"\\n\");\n }\n return normalized.split(\"\\n\");\n}\n\nexport function extractCodeWrapperAttributes(html: string): {\n preAttrs?: Record<string, string>;\n codeAttrs?: Record<string, string>;\n} {\n if (!html) {\n return {};\n }\n const preMatch = html.match(/<pre\\b([^>]*)>/i);\n const codeMatch = html.match(/<code\\b([^>]*)>/i);\n return {\n preAttrs: preMatch ? filterAllowedAttributes(parseAttributeFragment(preMatch[1] ?? \"\")) : undefined,\n codeAttrs: codeMatch ? filterAllowedAttributes(parseAttributeFragment(codeMatch[1] ?? \"\")) : undefined,\n };\n}\n\nfunction parseAttributeFragment(fragment: string): Record<string, string> {\n const attrs: Record<string, string> = {};\n const regex = /([a-zA-Z_:][\\w:.-]*)\\s*=\\s*\"([^\"]*)\"/g;\n let match: RegExpExecArray | null = regex.exec(fragment);\n while (match !== null) {\n const [, name, value] = match;\n attrs[name] = value;\n match = regex.exec(fragment);\n }\n return attrs;\n}\n\nfunction filterAllowedAttributes(attrs: Record<string, string>): Record<string, string> {\n const allowed = new Set([\"class\", \"style\", \"data-theme\"]);\n const filtered: Record<string, string> = {};\n for (const [key, value] of Object.entries(attrs)) {\n if (allowed.has(key) || key.startsWith(\"data-\")) {\n filtered[key] = value;\n }\n }\n return filtered;\n}\n"],"mappings":"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","names":["codeLines"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/code-highlighting.ts"],"sourcesContent":["export type HighlightedLine = string | null;\n\nfunction normalizeNewlines(input: string): string {\n return input.replace(/\\r\\n?/g, \"\\n\");\n}\n\nfunction isFenceLine(line: string): boolean {\n return /^```/.test(line.trim());\n}\n\nexport function stripCodeFence(raw: string): { code: string; info: string; hadFence: boolean } {\n if (!raw) {\n return { code: \"\", info: \"\", hadFence: false };\n }\n const normalized = normalizeNewlines(raw);\n const lines = normalized.split(\"\\n\");\n if (lines.length === 0) {\n return { code: normalized, info: \"\", hadFence: false };\n }\n\n const firstLine = lines[0];\n if (!isFenceLine(firstLine)) {\n return { code: normalized, info: \"\", hadFence: false };\n }\n\n const info = firstLine.slice(3).trim();\n let endIndex = lines.length - 1;\n while (endIndex > 0 && lines[endIndex].trim().length === 0) {\n endIndex--;\n }\n if (endIndex > 0 && isFenceLine(lines[endIndex])) {\n const codeLines = lines.slice(1, endIndex);\n return { code: codeLines.join(\"\\n\"), info, hadFence: true };\n }\n\n // During streaming, code block may not have closing fence yet\n // Still extract and return the info from the opening fence\n const codeLines = lines.slice(1);\n return { code: codeLines.join(\"\\n\"), info, hadFence: true };\n}\n\nfunction getDomParser(): DOMParser | null {\n if (typeof window !== \"undefined\" && typeof window.DOMParser === \"function\") {\n return new window.DOMParser();\n }\n if (typeof DOMParser !== \"undefined\") {\n try {\n return new DOMParser();\n } catch (e) {\n return null;\n }\n }\n return null;\n}\n\nexport function extractHighlightedLines(html: string, fallbackLength: number): HighlightedLine[] {\n if (!html) {\n return new Array(Math.max(0, fallbackLength)).fill(null);\n }\n\n const parser = getDomParser();\n if (parser) {\n try {\n const doc = parser.parseFromString(`<div>${html}</div>`, \"text/html\");\n const nodes = doc.querySelectorAll(\"span.line\");\n if (nodes.length > 0) {\n const lines: HighlightedLine[] = [];\n for (const node of nodes) {\n lines.push(node instanceof Element ? node.innerHTML : null);\n }\n return normalizeHighlightedLines(lines, fallbackLength);\n }\n } catch (error) {\n // fall through to manual extraction\n }\n }\n\n return manualExtractHighlightedLines(html, fallbackLength);\n}\n\nfunction normalizeHighlightedLines(lines: HighlightedLine[], fallbackLength: number): HighlightedLine[] {\n if (!lines || lines.length === 0) {\n return new Array(Math.max(0, fallbackLength)).fill(null);\n }\n const length = Math.max(fallbackLength, lines.length);\n const result: HighlightedLine[] = new Array(length).fill(null);\n for (let i = 0; i < lines.length; i++) {\n result[i] = lines[i];\n }\n return result;\n}\n\nfunction manualExtractHighlightedLines(html: string, fallbackLength: number): HighlightedLine[] {\n const lineRegex = /<span class=\"line\"(?:\\s+[^>]*)?>/gi;\n const lines: string[] = [];\n let match: RegExpExecArray | null = lineRegex.exec(html);\n\n while (match !== null) {\n const openTagEnd = html.indexOf(\">\", match.index);\n if (openTagEnd === -1) break;\n let cursor = openTagEnd + 1;\n let depth = 1;\n let buffer = \"\";\n\n while (cursor < html.length && depth > 0) {\n const nextTagStart = html.indexOf(\"<\", cursor);\n if (nextTagStart === -1) {\n buffer += html.slice(cursor);\n cursor = html.length;\n break;\n }\n if (nextTagStart > cursor) {\n buffer += html.slice(cursor, nextTagStart);\n }\n cursor = nextTagStart;\n if (html.startsWith(\"</span>\", cursor)) {\n depth -= 1;\n cursor += 7;\n if (depth === 0) break;\n buffer += \"</span>\";\n continue;\n }\n const tagEnd = html.indexOf(\">\", cursor);\n if (tagEnd === -1) {\n buffer += html.slice(cursor);\n cursor = html.length;\n break;\n }\n const tag = html.slice(cursor, tagEnd + 1);\n if (/^<span\\b/i.test(tag)) {\n depth += 1;\n }\n buffer += tag;\n cursor = tagEnd + 1;\n }\n\n lines.push(buffer);\n lineRegex.lastIndex = cursor;\n match = lineRegex.exec(html);\n }\n\n return normalizeHighlightedLines(lines, fallbackLength);\n}\n\nexport function dedentIndentedCode(raw: string): string {\n if (!raw) return \"\";\n const normalized = normalizeNewlines(raw);\n const lines = normalized.split(\"\\n\");\n let minIndent = Number.POSITIVE_INFINITY;\n for (const line of lines) {\n const match = line.match(/^\\s+/);\n if (!match) continue;\n minIndent = Math.min(minIndent, match[0].length);\n }\n if (!Number.isFinite(minIndent) || minIndent === 0) {\n return normalized;\n }\n return lines.map((line) => (line.startsWith(\" \".repeat(minIndent)) ? line.slice(minIndent) : line)).join(\"\\n\");\n}\n\nexport function extractCodeLines(raw: string): string[] {\n if (!raw) return [];\n const normalized = normalizeNewlines(raw);\n const { code, hadFence } = stripCodeFence(normalized);\n if (hadFence) {\n return code.split(\"\\n\");\n }\n if (/^\\s{4}/m.test(normalized)) {\n return dedentIndentedCode(normalized).split(\"\\n\");\n }\n return normalized.split(\"\\n\");\n}\n\nexport function extractCodeWrapperAttributes(html: string): {\n preAttrs?: Record<string, string>;\n codeAttrs?: Record<string, string>;\n} {\n if (!html) {\n return {};\n }\n const preMatch = html.match(/<pre\\b([^>]*)>/i);\n const codeMatch = html.match(/<code\\b([^>]*)>/i);\n return {\n preAttrs: preMatch ? filterAllowedAttributes(parseAttributeFragment(preMatch[1] ?? \"\")) : undefined,\n codeAttrs: codeMatch ? filterAllowedAttributes(parseAttributeFragment(codeMatch[1] ?? \"\")) : undefined,\n };\n}\n\nfunction parseAttributeFragment(fragment: string): Record<string, string> {\n const attrs: Record<string, string> = {};\n const regex = /([a-zA-Z_:][\\w:.-]*)\\s*=\\s*\"([^\"]*)\"/g;\n let match: RegExpExecArray | null = regex.exec(fragment);\n while (match !== null) {\n const [, name, value] = match;\n attrs[name] = value;\n match = regex.exec(fragment);\n }\n return attrs;\n}\n\nfunction filterAllowedAttributes(attrs: Record<string, string>): Record<string, string> {\n const allowed = new Set([\"class\", \"style\", \"data-theme\"]);\n const filtered: Record<string, string> = {};\n for (const [key, value] of Object.entries(attrs)) {\n if (allowed.has(key) || key.startsWith(\"data-\")) {\n filtered[key] = value;\n }\n }\n return filtered;\n}\n"],"mappings":"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","names":["codeLines"]}
|