@stream-mdx/core 0.0.3 → 0.1.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/CHANGELOG.md +6 -0
- package/dist/index.cjs +305 -46
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +304 -46
- package/dist/inline-parser.cjs +76 -3
- package/dist/inline-parser.d.cts +5 -0
- package/dist/inline-parser.d.ts +5 -0
- package/dist/inline-parser.mjs +76 -3
- package/dist/mixed-content.cjs +130 -9
- package/dist/mixed-content.d.cts +16 -2
- package/dist/mixed-content.d.ts +16 -2
- package/dist/mixed-content.mjs +130 -9
- package/dist/streaming/inline-streaming.cjs +99 -34
- package/dist/streaming/inline-streaming.d.cts +13 -2
- package/dist/streaming/inline-streaming.d.ts +13 -2
- package/dist/streaming/inline-streaming.mjs +98 -34
- package/dist/types.d.cts +22 -2
- package/dist/types.d.ts +22 -2
- package/dist/worker-html-sanitizer.cjs +16 -2
- package/dist/worker-html-sanitizer.mjs +16 -2
- package/package.json +2 -2
|
@@ -1,63 +1,127 @@
|
|
|
1
1
|
// src/streaming/inline-streaming.ts
|
|
2
|
+
var DEFAULT_FORMAT_ANTICIPATION = {
|
|
3
|
+
inline: false,
|
|
4
|
+
mathInline: false,
|
|
5
|
+
mathBlock: false,
|
|
6
|
+
html: false,
|
|
7
|
+
mdx: false,
|
|
8
|
+
regex: false
|
|
9
|
+
};
|
|
10
|
+
function normalizeFormatAnticipation(input) {
|
|
11
|
+
if (input === true) {
|
|
12
|
+
return { ...DEFAULT_FORMAT_ANTICIPATION, inline: true };
|
|
13
|
+
}
|
|
14
|
+
if (!input) {
|
|
15
|
+
return { ...DEFAULT_FORMAT_ANTICIPATION };
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
inline: input.inline ?? false,
|
|
19
|
+
mathInline: input.mathInline ?? false,
|
|
20
|
+
mathBlock: input.mathBlock ?? false,
|
|
21
|
+
html: input.html ?? false,
|
|
22
|
+
mdx: input.mdx ?? false,
|
|
23
|
+
regex: input.regex ?? false
|
|
24
|
+
};
|
|
25
|
+
}
|
|
2
26
|
function prepareInlineStreamingContent(content, options) {
|
|
3
|
-
const enableAnticipation = Boolean(options?.formatAnticipation);
|
|
4
27
|
const enableMath = options?.math !== false;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
28
|
+
const anticipation = normalizeFormatAnticipation(options?.formatAnticipation);
|
|
29
|
+
const enableInlineAnticipation = anticipation.inline;
|
|
30
|
+
const enableMathInlineAnticipation = anticipation.mathInline;
|
|
31
|
+
const enableMathBlockAnticipation = anticipation.mathBlock;
|
|
32
|
+
const stack = [];
|
|
33
|
+
const toggleToken = (token) => {
|
|
34
|
+
const last = stack[stack.length - 1];
|
|
35
|
+
if (last === token) {
|
|
36
|
+
stack.pop();
|
|
37
|
+
} else {
|
|
38
|
+
stack.push(token);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
let mathDisplayOpen = false;
|
|
42
|
+
let mathDisplayCrossedNewline = false;
|
|
10
43
|
for (let i = 0; i < content.length; i++) {
|
|
11
44
|
const code = content.charCodeAt(i);
|
|
12
|
-
if (code ===
|
|
13
|
-
|
|
45
|
+
if (code === 10 || code === 13) {
|
|
46
|
+
if (mathDisplayOpen) {
|
|
47
|
+
mathDisplayCrossedNewline = true;
|
|
48
|
+
}
|
|
14
49
|
continue;
|
|
15
50
|
}
|
|
16
51
|
if (code === 96) {
|
|
17
|
-
|
|
52
|
+
toggleToken("code");
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (code === 126 && i + 1 < content.length && content.charCodeAt(i + 1) === 126) {
|
|
56
|
+
toggleToken("strike");
|
|
57
|
+
i += 1;
|
|
18
58
|
continue;
|
|
19
59
|
}
|
|
20
60
|
if (code === 42) {
|
|
21
61
|
if (i + 1 < content.length && content.charCodeAt(i + 1) === 42) {
|
|
22
|
-
|
|
23
|
-
starCount += 2;
|
|
62
|
+
toggleToken("strong");
|
|
24
63
|
i += 1;
|
|
25
64
|
} else {
|
|
26
|
-
|
|
65
|
+
toggleToken("em");
|
|
27
66
|
}
|
|
28
67
|
continue;
|
|
29
68
|
}
|
|
30
|
-
if (code ===
|
|
31
|
-
if (i + 1 < content.length && content.charCodeAt(i + 1) ===
|
|
32
|
-
|
|
69
|
+
if (enableMath && code === 36) {
|
|
70
|
+
if (i + 1 < content.length && content.charCodeAt(i + 1) === 36) {
|
|
71
|
+
toggleToken("math-display");
|
|
72
|
+
if (mathDisplayOpen) {
|
|
73
|
+
mathDisplayOpen = false;
|
|
74
|
+
mathDisplayCrossedNewline = false;
|
|
75
|
+
} else {
|
|
76
|
+
mathDisplayOpen = true;
|
|
77
|
+
mathDisplayCrossedNewline = false;
|
|
78
|
+
}
|
|
33
79
|
i += 1;
|
|
80
|
+
} else {
|
|
81
|
+
toggleToken("math-inline");
|
|
34
82
|
}
|
|
35
83
|
}
|
|
36
84
|
}
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return { kind: "parse", status: "complete", content, appended: "" };
|
|
85
|
+
const hasIncompleteFormatting = stack.some((token) => token === "code" || token === "strike" || token === "strong" || token === "em");
|
|
86
|
+
const hasIncompleteMathInline = stack.includes("math-inline");
|
|
87
|
+
const hasIncompleteMathDisplay = stack.includes("math-display");
|
|
88
|
+
const hasIncompleteMath = hasIncompleteMathInline || hasIncompleteMathDisplay;
|
|
89
|
+
if (enableMath && hasIncompleteMath) {
|
|
90
|
+
if (hasIncompleteMathInline && !enableMathInlineAnticipation) {
|
|
91
|
+
return { kind: "raw", status: "raw", reason: "incomplete-math" };
|
|
92
|
+
}
|
|
93
|
+
if (hasIncompleteMathDisplay && (!enableMathBlockAnticipation || mathDisplayCrossedNewline)) {
|
|
94
|
+
return { kind: "raw", status: "raw", reason: "incomplete-math" };
|
|
95
|
+
}
|
|
49
96
|
}
|
|
50
|
-
if (!
|
|
97
|
+
if (hasIncompleteFormatting && !enableInlineAnticipation) {
|
|
51
98
|
return { kind: "raw", status: "raw", reason: "incomplete-formatting" };
|
|
52
99
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
100
|
+
if (!hasIncompleteFormatting && !hasIncompleteMath) {
|
|
101
|
+
return { kind: "parse", status: "complete", content, appended: "" };
|
|
102
|
+
}
|
|
103
|
+
const appendForToken = (token) => {
|
|
104
|
+
switch (token) {
|
|
105
|
+
case "code":
|
|
106
|
+
return "`";
|
|
107
|
+
case "strike":
|
|
108
|
+
return "~~";
|
|
109
|
+
case "strong":
|
|
110
|
+
return "**";
|
|
111
|
+
case "em":
|
|
112
|
+
return "*";
|
|
113
|
+
case "math-inline":
|
|
114
|
+
return "$";
|
|
115
|
+
case "math-display":
|
|
116
|
+
return "$$";
|
|
117
|
+
default:
|
|
118
|
+
return "";
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
const appended = stack.slice().reverse().map((token) => appendForToken(token)).join("");
|
|
59
122
|
return { kind: "parse", status: "anticipated", content: content + appended, appended };
|
|
60
123
|
}
|
|
61
124
|
export {
|
|
125
|
+
normalizeFormatAnticipation,
|
|
62
126
|
prepareInlineStreamingContent
|
|
63
127
|
};
|
package/dist/types.d.cts
CHANGED
|
@@ -39,6 +39,14 @@ interface MixedContentSegment {
|
|
|
39
39
|
status?: "pending" | "compiled" | "error";
|
|
40
40
|
error?: string;
|
|
41
41
|
}
|
|
42
|
+
type FormatAnticipationConfig = boolean | {
|
|
43
|
+
inline?: boolean;
|
|
44
|
+
mathInline?: boolean;
|
|
45
|
+
mathBlock?: boolean;
|
|
46
|
+
html?: boolean;
|
|
47
|
+
mdx?: boolean;
|
|
48
|
+
regex?: boolean;
|
|
49
|
+
};
|
|
42
50
|
interface InlineHtmlDescriptor {
|
|
43
51
|
tagName: string;
|
|
44
52
|
attributes: Record<string, string>;
|
|
@@ -115,8 +123,9 @@ type WorkerIn = {
|
|
|
115
123
|
tables?: boolean;
|
|
116
124
|
callouts?: boolean;
|
|
117
125
|
math?: boolean;
|
|
118
|
-
formatAnticipation?:
|
|
126
|
+
formatAnticipation?: FormatAnticipationConfig;
|
|
119
127
|
liveCodeHighlighting?: boolean;
|
|
128
|
+
mdxComponentNames?: string[];
|
|
120
129
|
};
|
|
121
130
|
mdx?: {
|
|
122
131
|
compileMode?: "server" | "worker";
|
|
@@ -182,6 +191,17 @@ interface RegexInlinePlugin extends InlinePlugin {
|
|
|
182
191
|
* the regex is skipped for that node.
|
|
183
192
|
*/
|
|
184
193
|
fastCheck?: (text: string) => boolean;
|
|
194
|
+
/**
|
|
195
|
+
* Optional streaming anticipation config. Only used when formatAnticipation.regex is enabled.
|
|
196
|
+
*/
|
|
197
|
+
anticipation?: RegexAnticipationPattern;
|
|
198
|
+
}
|
|
199
|
+
interface RegexAnticipationPattern {
|
|
200
|
+
start: RegExp;
|
|
201
|
+
end: RegExp;
|
|
202
|
+
full?: RegExp;
|
|
203
|
+
append: string | ((match: RegExpExecArray, content: string) => string);
|
|
204
|
+
maxScanChars?: number;
|
|
185
205
|
}
|
|
186
206
|
interface ASTInlinePlugin extends InlinePlugin {
|
|
187
207
|
visit: (node: InlineNode, ctx: {
|
|
@@ -314,4 +334,4 @@ interface CoalescingMetrics {
|
|
|
314
334
|
insertChildCoalesced: number;
|
|
315
335
|
}
|
|
316
336
|
|
|
317
|
-
export { type ASTInlinePlugin, type Block, type CoalescingMetrics, type CompiledMdxModule, type InlineHtmlDescriptor, type InlineNode, type InlinePlugin, LANGUAGE_ALIASES, type MixedContentSegment, type NodePath, type NodeSnapshot, PATCH_ROOT_ID, type Patch, type PatchMetrics, type PerformanceMetrics, type ProtectedRange, type ProtectedRangeKind, type RegexInlinePlugin, type SetPropsBatchEntry, type WorkerErrorPayload, type WorkerIn, type WorkerOut, type WorkerPhase };
|
|
337
|
+
export { type ASTInlinePlugin, type Block, type CoalescingMetrics, type CompiledMdxModule, type FormatAnticipationConfig, type InlineHtmlDescriptor, type InlineNode, type InlinePlugin, LANGUAGE_ALIASES, type MixedContentSegment, type NodePath, type NodeSnapshot, PATCH_ROOT_ID, type Patch, type PatchMetrics, type PerformanceMetrics, type ProtectedRange, type ProtectedRangeKind, type RegexAnticipationPattern, type RegexInlinePlugin, type SetPropsBatchEntry, type WorkerErrorPayload, type WorkerIn, type WorkerOut, type WorkerPhase };
|
package/dist/types.d.ts
CHANGED
|
@@ -39,6 +39,14 @@ interface MixedContentSegment {
|
|
|
39
39
|
status?: "pending" | "compiled" | "error";
|
|
40
40
|
error?: string;
|
|
41
41
|
}
|
|
42
|
+
type FormatAnticipationConfig = boolean | {
|
|
43
|
+
inline?: boolean;
|
|
44
|
+
mathInline?: boolean;
|
|
45
|
+
mathBlock?: boolean;
|
|
46
|
+
html?: boolean;
|
|
47
|
+
mdx?: boolean;
|
|
48
|
+
regex?: boolean;
|
|
49
|
+
};
|
|
42
50
|
interface InlineHtmlDescriptor {
|
|
43
51
|
tagName: string;
|
|
44
52
|
attributes: Record<string, string>;
|
|
@@ -115,8 +123,9 @@ type WorkerIn = {
|
|
|
115
123
|
tables?: boolean;
|
|
116
124
|
callouts?: boolean;
|
|
117
125
|
math?: boolean;
|
|
118
|
-
formatAnticipation?:
|
|
126
|
+
formatAnticipation?: FormatAnticipationConfig;
|
|
119
127
|
liveCodeHighlighting?: boolean;
|
|
128
|
+
mdxComponentNames?: string[];
|
|
120
129
|
};
|
|
121
130
|
mdx?: {
|
|
122
131
|
compileMode?: "server" | "worker";
|
|
@@ -182,6 +191,17 @@ interface RegexInlinePlugin extends InlinePlugin {
|
|
|
182
191
|
* the regex is skipped for that node.
|
|
183
192
|
*/
|
|
184
193
|
fastCheck?: (text: string) => boolean;
|
|
194
|
+
/**
|
|
195
|
+
* Optional streaming anticipation config. Only used when formatAnticipation.regex is enabled.
|
|
196
|
+
*/
|
|
197
|
+
anticipation?: RegexAnticipationPattern;
|
|
198
|
+
}
|
|
199
|
+
interface RegexAnticipationPattern {
|
|
200
|
+
start: RegExp;
|
|
201
|
+
end: RegExp;
|
|
202
|
+
full?: RegExp;
|
|
203
|
+
append: string | ((match: RegExpExecArray, content: string) => string);
|
|
204
|
+
maxScanChars?: number;
|
|
185
205
|
}
|
|
186
206
|
interface ASTInlinePlugin extends InlinePlugin {
|
|
187
207
|
visit: (node: InlineNode, ctx: {
|
|
@@ -314,4 +334,4 @@ interface CoalescingMetrics {
|
|
|
314
334
|
insertChildCoalesced: number;
|
|
315
335
|
}
|
|
316
336
|
|
|
317
|
-
export { type ASTInlinePlugin, type Block, type CoalescingMetrics, type CompiledMdxModule, type InlineHtmlDescriptor, type InlineNode, type InlinePlugin, LANGUAGE_ALIASES, type MixedContentSegment, type NodePath, type NodeSnapshot, PATCH_ROOT_ID, type Patch, type PatchMetrics, type PerformanceMetrics, type ProtectedRange, type ProtectedRangeKind, type RegexInlinePlugin, type SetPropsBatchEntry, type WorkerErrorPayload, type WorkerIn, type WorkerOut, type WorkerPhase };
|
|
337
|
+
export { type ASTInlinePlugin, type Block, type CoalescingMetrics, type CompiledMdxModule, type FormatAnticipationConfig, type InlineHtmlDescriptor, type InlineNode, type InlinePlugin, LANGUAGE_ALIASES, type MixedContentSegment, type NodePath, type NodeSnapshot, PATCH_ROOT_ID, type Patch, type PatchMetrics, type PerformanceMetrics, type ProtectedRange, type ProtectedRangeKind, type RegexAnticipationPattern, type RegexInlinePlugin, type SetPropsBatchEntry, type WorkerErrorPayload, type WorkerIn, type WorkerOut, type WorkerPhase };
|
|
@@ -37,9 +37,23 @@ var rehypeParse = __toESM(require("rehype-parse"), 1);
|
|
|
37
37
|
var rehypeSanitize = __toESM(require("rehype-sanitize"), 1);
|
|
38
38
|
var rehypeStringify = __toESM(require("rehype-stringify"), 1);
|
|
39
39
|
var import_unified = require("unified");
|
|
40
|
-
var
|
|
40
|
+
var rehypeSanitizeModule = rehypeSanitize;
|
|
41
|
+
var defaultSchema = rehypeSanitizeModule.defaultSchema;
|
|
42
|
+
var resolvePlugin = (mod) => {
|
|
43
|
+
if (typeof mod === "function") return mod;
|
|
44
|
+
if (mod && typeof mod.default === "function") {
|
|
45
|
+
return mod.default;
|
|
46
|
+
}
|
|
47
|
+
if (mod && typeof mod.default?.default === "function") {
|
|
48
|
+
return mod.default?.default;
|
|
49
|
+
}
|
|
50
|
+
return mod;
|
|
51
|
+
};
|
|
52
|
+
var rehypeParsePlugin = resolvePlugin(rehypeParse);
|
|
53
|
+
var rehypeSanitizePlugin = resolvePlugin(rehypeSanitizeModule);
|
|
54
|
+
var rehypeStringifyPlugin = resolvePlugin(rehypeStringify);
|
|
41
55
|
var SANITIZED_SCHEMA = createSchema();
|
|
42
|
-
var sanitizeProcessor = (0, import_unified.unified)().use(
|
|
56
|
+
var sanitizeProcessor = (0, import_unified.unified)().use(rehypeParsePlugin, { fragment: true }).use(rehypeSanitizePlugin, SANITIZED_SCHEMA).use(rehypeStringifyPlugin).freeze();
|
|
43
57
|
function sanitizeHtmlInWorker(html) {
|
|
44
58
|
if (!html) return "";
|
|
45
59
|
try {
|
|
@@ -3,9 +3,23 @@ import * as rehypeParse from "rehype-parse";
|
|
|
3
3
|
import * as rehypeSanitize from "rehype-sanitize";
|
|
4
4
|
import * as rehypeStringify from "rehype-stringify";
|
|
5
5
|
import { unified } from "unified";
|
|
6
|
-
var
|
|
6
|
+
var rehypeSanitizeModule = rehypeSanitize;
|
|
7
|
+
var defaultSchema = rehypeSanitizeModule.defaultSchema;
|
|
8
|
+
var resolvePlugin = (mod) => {
|
|
9
|
+
if (typeof mod === "function") return mod;
|
|
10
|
+
if (mod && typeof mod.default === "function") {
|
|
11
|
+
return mod.default;
|
|
12
|
+
}
|
|
13
|
+
if (mod && typeof mod.default?.default === "function") {
|
|
14
|
+
return mod.default?.default;
|
|
15
|
+
}
|
|
16
|
+
return mod;
|
|
17
|
+
};
|
|
18
|
+
var rehypeParsePlugin = resolvePlugin(rehypeParse);
|
|
19
|
+
var rehypeSanitizePlugin = resolvePlugin(rehypeSanitizeModule);
|
|
20
|
+
var rehypeStringifyPlugin = resolvePlugin(rehypeStringify);
|
|
7
21
|
var SANITIZED_SCHEMA = createSchema();
|
|
8
|
-
var sanitizeProcessor = unified().use(
|
|
22
|
+
var sanitizeProcessor = unified().use(rehypeParsePlugin, { fragment: true }).use(rehypeSanitizePlugin, SANITIZED_SCHEMA).use(rehypeStringifyPlugin).freeze();
|
|
9
23
|
function sanitizeHtmlInWorker(html) {
|
|
10
24
|
if (!html) return "";
|
|
11
25
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream-mdx/core",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Core types, snapshot utilities, and perf helpers for the Streaming Markdown V2 stack",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"sideEffects": false,
|
|
91
91
|
"scripts": {
|
|
92
92
|
"build": "tsup",
|
|
93
|
-
"test": "tsx
|
|
93
|
+
"test": "tsx ../../scripts/run-tests.ts __tests__",
|
|
94
94
|
"clean": "rm -rf dist",
|
|
95
95
|
"prepack": "npm run build"
|
|
96
96
|
},
|