@janhq/remend 1.0.1

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 ADDED
@@ -0,0 +1,134 @@
1
+ # Remend
2
+
3
+ Self-healing markdown. Intelligently parses and styles incomplete Markdown blocks.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/remend)](https://www.npmjs.com/package/remend)
6
+
7
+ ## Overview
8
+
9
+ Remend is a lightweight utility that handles incomplete Markdown syntax during streaming. When AI models stream Markdown token-by-token, you often get partial formatting markers like unclosed `**bold**` or incomplete `[links](`. Remend automatically completes these unterminated blocks so they render correctly in real-time.
10
+
11
+ Remend powers the markdown termination logic in [Streamdown](https://streamdown.ai) and can be used standalone in any streaming Markdown application.
12
+
13
+ ## Features
14
+
15
+ - 🔄 **Streaming-optimized** - Handles incomplete Markdown gracefully
16
+ - 🎨 **Smart completion** - Auto-closes bold, italic, code, links, images, strikethrough, and math blocks
17
+ - ⚡ **Performance-first** - Optimized string operations, no regex allocations
18
+ - 🛡️ **Context-aware** - Respects code blocks, math blocks, and nested formatting
19
+ - 🎯 **Edge case handling** - List markers, word-internal characters, escaped sequences
20
+ - 📦 **Zero dependencies** - Pure TypeScript implementation
21
+
22
+ ## Supported Syntax
23
+
24
+ Remend intelligently completes the following incomplete Markdown patterns:
25
+
26
+ - **Bold**: `**text` → `**text**`
27
+ - **Italic**: `*text` or `_text` → `*text*` or `_text_`
28
+ - **Bold + Italic**: `***text` → `***text***`
29
+ - **Inline code**: `` `code `` → `` `code` ``
30
+ - **Strikethrough**: `~~text` → `~~text~~`
31
+ - **Links**: `[text](url` → `[text](streamdown:incomplete-link)`
32
+ - **Images**: `![alt](url` → removed (can't display partial images)
33
+ - **Block math**: `$$formula` → `$$formula$$`
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ npm i remend
39
+ ```
40
+
41
+ ## Usage
42
+
43
+ ```typescript
44
+ import remend from "remend";
45
+
46
+ // During streaming
47
+ const partialMarkdown = "This is **bold text";
48
+ const completed = remend(partialMarkdown);
49
+ // Result: "This is **bold text**"
50
+
51
+ // With incomplete link
52
+ const partialLink = "Check out [this link](https://exampl";
53
+ const completed = remend(partialLink);
54
+ // Result: "Check out [this link](streamdown:incomplete-link)"
55
+ ```
56
+
57
+ ### Configuration
58
+
59
+ You can selectively disable specific completions by passing an options object. All options default to `true`:
60
+
61
+ ```typescript
62
+ import remend from "remend";
63
+
64
+ // Disable link and KaTeX completion
65
+ const completed = remend(partialMarkdown, {
66
+ links: false,
67
+ katex: false,
68
+ });
69
+ ```
70
+
71
+ Available options:
72
+
73
+ | Option | Description |
74
+ |--------|-------------|
75
+ | `links` | Complete incomplete links |
76
+ | `images` | Complete incomplete images |
77
+ | `bold` | Complete bold formatting (`**`) |
78
+ | `italic` | Complete italic formatting (`*` and `_`) |
79
+ | `boldItalic` | Complete bold-italic formatting (`***`) |
80
+ | `inlineCode` | Complete inline code formatting (`` ` ``) |
81
+ | `strikethrough` | Complete strikethrough formatting (`~~`) |
82
+ | `katex` | Complete block KaTeX math (`$$`) |
83
+ | `setextHeadings` | Handle incomplete setext headings |
84
+
85
+ ### Usage with Remark
86
+
87
+ Remend is a preprocessor that must be run on the raw Markdown string **before** passing it into the unified/remark processing pipeline:
88
+
89
+ ```typescript
90
+ import remend from "remend";
91
+ import { unified } from "unified";
92
+ import remarkParse from "remark-parse";
93
+ import remarkRehype from "remark-rehype";
94
+ import rehypeStringify from "rehype-stringify";
95
+
96
+ const streamedMarkdown = "This is **incomplete bold";
97
+
98
+ // Run Remend first to complete incomplete syntax
99
+ const completedMarkdown = remend(streamedMarkdown);
100
+
101
+ // Then process with unified
102
+ const file = await unified()
103
+ .use(remarkParse)
104
+ .use(remarkRehype)
105
+ .use(rehypeStringify)
106
+ .process(completedMarkdown);
107
+
108
+ console.log(String(file));
109
+ ```
110
+
111
+ This is important because Remend operates on the raw string level, while remark/unified work with abstract syntax trees (ASTs). Running Remend after parsing would be ineffective.
112
+
113
+ ## How It Works
114
+
115
+ Remend analyzes the input text and:
116
+
117
+ 1. Detects incomplete formatting markers at the end of the text
118
+ 2. Counts opening vs closing markers (considering escaped characters)
119
+ 3. Intelligently adds closing markers when needed
120
+ 4. Respects context like code blocks, math blocks, and list items
121
+ 5. Handles edge cases like nested brackets and word-internal characters
122
+
123
+ The parser is designed to be defensive and only completes formatting when it's unambiguous that the block is incomplete.
124
+
125
+ ## Performance
126
+
127
+ Remend is built for high-performance streaming scenarios:
128
+
129
+ - Direct string iteration instead of regex splits
130
+ - ASCII fast-path for common characters
131
+ - Minimal memory allocations
132
+ - Early returns for common cases
133
+
134
+ For more info, see the [documentation](https://streamdown.ai/docs/termination).
package/dist/index.cjs ADDED
@@ -0,0 +1,18 @@
1
+ 'use strict';var p=/(\*\*)([^*]*?)$/,I=/(__)([^_]*?)$/,P=/(\*\*\*)([^*]*?)$/,B=/(\*)([^*]*?)$/,$=/(_)([^_]*?)$/,M=/(`)([^`]*?)$/,A=/(~~)([^~]*?)$/,c=/^[\s_~*`]*$/,k=/^[\s]*[-*+][\s]+$/,C=/[\p{L}\p{N}_]/u,S=/^```[^`\n]*```?$/,_=/^\*{4,}$/;var a=n=>{if(!n)return false;let r=n.charCodeAt(0);return r>=48&&r<=57||r>=65&&r<=90||r>=97&&r<=122||r===95?true:C.test(n)},g=(n,r)=>{let e=false;for(let s=0;s<r;s+=1)n[s]==="`"&&n[s+1]==="`"&&n[s+2]==="`"&&(e=!e,s+=2);return e},O=(n,r)=>{let e=1;for(let s=r-1;s>=0;s-=1)if(n[s]==="]")e+=1;else if(n[s]==="["&&(e-=1,e===0))return s;return -1},T=(n,r)=>{let e=1;for(let s=r+1;s<n.length;s+=1)if(n[s]==="[")e+=1;else if(n[s]==="]"&&(e-=1,e===0))return s;return -1},d=(n,r)=>{let e=false,s=false;for(let i=0;i<n.length&&i<r;i+=1){if(n[i]==="\\"&&n[i+1]==="$"){i+=1;continue}n[i]==="$"&&(n[i+1]==="$"?(s=!s,i+=1,e=false):s||(e=!e));}return e||s},z=(n,r)=>{for(let e=r;e<n.length;e+=1){if(n[e]===")")return true;if(n[e]===`
2
+ `)return false}return false},b=(n,r)=>{for(let e=r-1;e>=0;e-=1){if(n[e]===")")return false;if(n[e]==="(")return e>0&&n[e-1]==="]"?z(n,r):false;if(n[e]===`
3
+ `)return false}return false},h=(n,r,e)=>{let s=0;for(let o=r-1;o>=0;o-=1)if(n[o]===`
4
+ `){s=o+1;break}let i=n.length;for(let o=r;o<n.length;o+=1)if(n[o]===`
5
+ `){i=o;break}let l=n.substring(s,i),t=0,f=false;for(let o of l)if(o===e)t+=1;else if(o!==" "&&o!==" "){f=true;break}return t>=3&&!f};var Q=(n,r,e)=>{if(e!==" "&&e!==" ")return false;let s=0;for(let i=r-1;i>=0;i-=1)if(n[i]===`
6
+ `){s=i+1;break}for(let i=s;i<r;i+=1)if(n[i]!==" "&&n[i]!==" ")return false;return true},Y=(n,r,e,s)=>e==="\\"||n.includes("$")&&d(n,r)?true:e!=="*"&&s==="*"?(r<n.length-2?n[r+2]:"")!=="*":!!(e==="*"||e&&s&&a(e)&&a(s)||Q(n,r,s)),U=n=>{let r=0,e=n.length;for(let s=0;s<e;s+=1){if(n[s]!=="*")continue;let i=s>0?n[s-1]:"",l=s<e-1?n[s+1]:"";Y(n,s,i,l)||(r+=1);}return r},j=(n,r,e,s)=>!!(e==="\\"||n.includes("$")&&d(n,r)||b(n,r)||e==="_"||s==="_"||e&&s&&a(e)&&a(s)),q=n=>{let r=0,e=n.length;for(let s=0;s<e;s+=1){if(n[s]!=="_")continue;let i=s>0?n[s-1]:"",l=s<e-1?n[s+1]:"";j(n,s,i,l)||(r+=1);}return r},G=n=>{let r=0,e=0;for(let s=0;s<n.length;s+=1)n[s]==="*"?e+=1:(e>=3&&(r+=Math.floor(e/3)),e=0);return e>=3&&(r+=Math.floor(e/3)),r},J=(n,r,e)=>{if(!r||c.test(r))return true;let i=n.substring(0,e).lastIndexOf(`
7
+ `),l=i===-1?0:i+1,t=n.substring(l,e);return k.test(t)&&r.includes(`
8
+ `)?true:h(n,e,"*")},N=n=>{let r=n.match(p);if(!r)return n;let e=r[2],s=n.lastIndexOf(r[1]);return g(n,s)||J(n,e,s)?n:(n.match(/\*\*/g)||[]).length%2===1?`${n}**`:n},V=(n,r,e)=>{if(!r||c.test(r))return true;let i=n.substring(0,e).lastIndexOf(`
9
+ `),l=i===-1?0:i+1,t=n.substring(l,e);return k.test(t)&&r.includes(`
10
+ `)?true:h(n,e,"_")},w=n=>{let r=n.match(I);if(!r)return n;let e=r[2],s=n.lastIndexOf(r[1]);return g(n,s)||V(n,e,s)?n:(n.match(/__/g)||[]).length%2===1?`${n}__`:n},X=n=>{for(let r=0;r<n.length;r+=1)if(n[r]==="*"&&n[r-1]!=="*"&&n[r+1]!=="*"&&n[r-1]!=="\\"&&!d(n,r)){let e=r>0?n[r-1]:"",s=r<n.length-1?n[r+1]:"";if(e&&s&&a(e)&&a(s))continue;return r}return -1},L=n=>{if(!n.match(B))return n;let e=X(n);if(e===-1||g(n,e))return n;let s=n.substring(e+1);return !s||c.test(s)?n:U(n)%2===1?`${n}*`:n},W=n=>{for(let r=0;r<n.length;r+=1)if(n[r]==="_"&&n[r-1]!=="_"&&n[r+1]!=="_"&&n[r-1]!=="\\"&&!d(n,r)&&!b(n,r)){let e=r>0?n[r-1]:"",s=r<n.length-1?n[r+1]:"";if(e&&s&&a(e)&&a(s))continue;return r}return -1},Z=n=>{let r=n.length;for(;r>0&&n[r-1]===`
11
+ `;)r-=1;if(r<n.length){let e=n.slice(0,r),s=n.slice(r);return `${e}_${s}`}return `${n}_`},x=n=>{if(!n.endsWith("**"))return null;let r=n.slice(0,-2);if((r.match(/\*\*/g)||[]).length%2!==1)return null;let s=r.indexOf("**"),i=W(r);return s!==-1&&i!==-1&&s<i?`${r}_**`:null},R=n=>{if(!n.match($))return n;let e=W(n);if(e===-1||g(n,e))return n;let s=n.substring(e+1);if(!s||c.test(s))return n;if(q(n)%2===1){let l=x(n);return l!==null?l:Z(n)}return n},nn=n=>{let r=(n.match(/\*\*/g)||[]).length,e=U(n);return r%2===0&&e%2===0},rn=(n,r,e)=>!r||c.test(r)||g(n,e)?true:h(n,e,"*"),E=n=>{if(_.test(n))return n;let r=n.match(P);if(!r)return n;let e=r[2],s=n.lastIndexOf(r[1]);return rn(n,e,s)?n:G(n)%2===1?nn(n)?n:`${n}***`:n};var m=(n,r)=>{let e=false,s=false;for(let i=0;i<r;i+=1){if(n.substring(i,i+3)==="```"){s=!s,i+=2;continue}!s&&n[i]==="`"&&(e=!e);}return e||s},en=(n,r)=>{let e=n.substring(r,r+3)==="```",s=r>0&&n.substring(r-1,r+2)==="```",i=r>1&&n.substring(r-2,r+1)==="```";return e||s||i},H=n=>{let r=0;for(let e=0;e<n.length;e+=1)n[e]==="`"&&!en(n,e)&&(r+=1);return r};var sn=n=>!n.match(S)||n.includes(`
12
+ `)?null:n.endsWith("``")&&!n.endsWith("```")?`${n}\``:n,tn=n=>(n.match(/```/g)||[]).length%2===1,D=n=>{let r=sn(n);if(r!==null)return r;let e=n.match(M);if(e&&!tn(n)){let s=e[2];if(!s||c.test(s))return n;if(H(n)%2===1)return `${n}\``}return n};var ln=(n,r)=>r>=2&&n.substring(r-2,r+1)==="```"||r>=1&&n.substring(r-1,r+2)==="```"||r<=n.length-3&&n.substring(r,r+3)==="```",on=n=>{let r=0,e=false;for(let s=0;s<n.length-1;s+=1)n[s]==="`"&&!ln(n,s)&&(e=!e),!e&&n[s]==="$"&&n[s+1]==="$"&&(r+=1,s+=1);return r},cn=n=>{let r=n.indexOf("$$");return r!==-1&&n.indexOf(`
13
+ `,r)!==-1&&!n.endsWith(`
14
+ `)?`${n}
15
+ $$`:`${n}$$`},F=n=>on(n)%2===0?n:cn(n);var an=(n,r)=>{if(n.substring(r+2).includes(")"))return null;let s=O(n,r);if(s===-1||m(n,s))return null;let i=s>0&&n[s-1]==="!",l=i?s-1:s,t=n.substring(0,l);if(i)return t;let f=n.substring(s+1,r);return `${t}[${f}](streamdown:incomplete-link)`},un=(n,r)=>{let e=r>0&&n[r-1]==="!",s=e?r-1:r;if(!n.substring(r+1).includes("]")){let t=n.substring(0,s);return e?t:`${n}](streamdown:incomplete-link)`}if(T(n,r)===-1){let t=n.substring(0,s);return e?t:`${n}](streamdown:incomplete-link)`}return null},v=n=>{let r=n.lastIndexOf("](");if(r!==-1&&!m(n,r)){let e=an(n,r);if(e!==null)return e}for(let e=n.length-1;e>=0;e-=1)if(n[e]==="["&&!m(n,e)){let s=un(n,e);if(s!==null)return s}return n};var fn=/^-{1,2}$/,gn=/^[\s]*-{1,2}[\s]+$/,dn=/^={1,2}$/,hn=/^[\s]*={1,2}[\s]+$/,y=n=>{if(!n||typeof n!="string")return n;let r=n.lastIndexOf(`
16
+ `);if(r===-1)return n;let e=n.substring(r+1),s=n.substring(0,r),i=e.trim();if(fn.test(i)&&!e.match(gn)){let t=s.split(`
17
+ `).at(-1);if(t&&t.trim().length>0)return `${n}\u200B`}if(dn.test(i)&&!e.match(hn)){let t=s.split(`
18
+ `).at(-1);if(t&&t.trim().length>0)return `${n}\u200B`}return n};var K=n=>{let r=n.match(A);if(r){let e=r[2];if(!e||c.test(e))return n;if((n.match(/~~/g)||[]).length%2===1)return `${n}~~`}return n};var u=n=>n!==false,mn=(n,r)=>{if(!n||typeof n!="string")return n;let e=n.endsWith(" ")&&!n.endsWith(" ")?n.slice(0,-1):n;if(u(r==null?void 0:r.setextHeadings)&&(e=y(e)),u(r==null?void 0:r.links)||u(r==null?void 0:r.images)){let s=v(e);if(s.endsWith("](streamdown:incomplete-link)"))return s;e=s;}return u(r==null?void 0:r.boldItalic)&&(e=E(e)),u(r==null?void 0:r.bold)&&(e=N(e)),u(r==null?void 0:r.italic)&&(e=w(e),e=L(e),e=R(e)),u(r==null?void 0:r.inlineCode)&&(e=D(e)),u(r==null?void 0:r.strikethrough)&&(e=K(e)),u(r==null?void 0:r.katex)&&(e=F(e)),e},Fn=mn;module.exports=Fn;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Configuration options for the remend function.
3
+ * All options default to `true` when not specified.
4
+ * Set an option to `false` to disable that specific completion.
5
+ */
6
+ type RemendOptions = {
7
+ /** Complete links and images (e.g., `[text](url` → `[text](streamdown:incomplete-link)`) */
8
+ links?: boolean;
9
+ /** Complete images (e.g., `![alt](url` → removed) */
10
+ images?: boolean;
11
+ /** Complete bold formatting (e.g., `**text` → `**text**`) */
12
+ bold?: boolean;
13
+ /** Complete italic formatting (e.g., `*text` → `*text*` or `_text` → `_text_`) */
14
+ italic?: boolean;
15
+ /** Complete bold-italic formatting (e.g., `***text` → `***text***`) */
16
+ boldItalic?: boolean;
17
+ /** Complete inline code formatting (e.g., `` `code `` → `` `code` ``) */
18
+ inlineCode?: boolean;
19
+ /** Complete strikethrough formatting (e.g., `~~text` → `~~text~~`) */
20
+ strikethrough?: boolean;
21
+ /** Complete block KaTeX math (e.g., `$$equation` → `$$equation$$`) */
22
+ katex?: boolean;
23
+ /** Handle incomplete setext headings to prevent misinterpretation */
24
+ setextHeadings?: boolean;
25
+ };
26
+ declare const remend: (text: string, options?: RemendOptions) => string;
27
+
28
+ export { type RemendOptions, remend as default };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Configuration options for the remend function.
3
+ * All options default to `true` when not specified.
4
+ * Set an option to `false` to disable that specific completion.
5
+ */
6
+ type RemendOptions = {
7
+ /** Complete links and images (e.g., `[text](url` → `[text](streamdown:incomplete-link)`) */
8
+ links?: boolean;
9
+ /** Complete images (e.g., `![alt](url` → removed) */
10
+ images?: boolean;
11
+ /** Complete bold formatting (e.g., `**text` → `**text**`) */
12
+ bold?: boolean;
13
+ /** Complete italic formatting (e.g., `*text` → `*text*` or `_text` → `_text_`) */
14
+ italic?: boolean;
15
+ /** Complete bold-italic formatting (e.g., `***text` → `***text***`) */
16
+ boldItalic?: boolean;
17
+ /** Complete inline code formatting (e.g., `` `code `` → `` `code` ``) */
18
+ inlineCode?: boolean;
19
+ /** Complete strikethrough formatting (e.g., `~~text` → `~~text~~`) */
20
+ strikethrough?: boolean;
21
+ /** Complete block KaTeX math (e.g., `$$equation` → `$$equation$$`) */
22
+ katex?: boolean;
23
+ /** Handle incomplete setext headings to prevent misinterpretation */
24
+ setextHeadings?: boolean;
25
+ };
26
+ declare const remend: (text: string, options?: RemendOptions) => string;
27
+
28
+ export { type RemendOptions, remend as default };
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ var p=/(\*\*)([^*]*?)$/,I=/(__)([^_]*?)$/,P=/(\*\*\*)([^*]*?)$/,B=/(\*)([^*]*?)$/,$=/(_)([^_]*?)$/,M=/(`)([^`]*?)$/,A=/(~~)([^~]*?)$/,c=/^[\s_~*`]*$/,k=/^[\s]*[-*+][\s]+$/,C=/[\p{L}\p{N}_]/u,S=/^```[^`\n]*```?$/,_=/^\*{4,}$/;var a=n=>{if(!n)return false;let r=n.charCodeAt(0);return r>=48&&r<=57||r>=65&&r<=90||r>=97&&r<=122||r===95?true:C.test(n)},g=(n,r)=>{let e=false;for(let s=0;s<r;s+=1)n[s]==="`"&&n[s+1]==="`"&&n[s+2]==="`"&&(e=!e,s+=2);return e},O=(n,r)=>{let e=1;for(let s=r-1;s>=0;s-=1)if(n[s]==="]")e+=1;else if(n[s]==="["&&(e-=1,e===0))return s;return -1},T=(n,r)=>{let e=1;for(let s=r+1;s<n.length;s+=1)if(n[s]==="[")e+=1;else if(n[s]==="]"&&(e-=1,e===0))return s;return -1},d=(n,r)=>{let e=false,s=false;for(let i=0;i<n.length&&i<r;i+=1){if(n[i]==="\\"&&n[i+1]==="$"){i+=1;continue}n[i]==="$"&&(n[i+1]==="$"?(s=!s,i+=1,e=false):s||(e=!e));}return e||s},z=(n,r)=>{for(let e=r;e<n.length;e+=1){if(n[e]===")")return true;if(n[e]===`
2
+ `)return false}return false},b=(n,r)=>{for(let e=r-1;e>=0;e-=1){if(n[e]===")")return false;if(n[e]==="(")return e>0&&n[e-1]==="]"?z(n,r):false;if(n[e]===`
3
+ `)return false}return false},h=(n,r,e)=>{let s=0;for(let o=r-1;o>=0;o-=1)if(n[o]===`
4
+ `){s=o+1;break}let i=n.length;for(let o=r;o<n.length;o+=1)if(n[o]===`
5
+ `){i=o;break}let l=n.substring(s,i),t=0,f=false;for(let o of l)if(o===e)t+=1;else if(o!==" "&&o!==" "){f=true;break}return t>=3&&!f};var Q=(n,r,e)=>{if(e!==" "&&e!==" ")return false;let s=0;for(let i=r-1;i>=0;i-=1)if(n[i]===`
6
+ `){s=i+1;break}for(let i=s;i<r;i+=1)if(n[i]!==" "&&n[i]!==" ")return false;return true},Y=(n,r,e,s)=>e==="\\"||n.includes("$")&&d(n,r)?true:e!=="*"&&s==="*"?(r<n.length-2?n[r+2]:"")!=="*":!!(e==="*"||e&&s&&a(e)&&a(s)||Q(n,r,s)),U=n=>{let r=0,e=n.length;for(let s=0;s<e;s+=1){if(n[s]!=="*")continue;let i=s>0?n[s-1]:"",l=s<e-1?n[s+1]:"";Y(n,s,i,l)||(r+=1);}return r},j=(n,r,e,s)=>!!(e==="\\"||n.includes("$")&&d(n,r)||b(n,r)||e==="_"||s==="_"||e&&s&&a(e)&&a(s)),q=n=>{let r=0,e=n.length;for(let s=0;s<e;s+=1){if(n[s]!=="_")continue;let i=s>0?n[s-1]:"",l=s<e-1?n[s+1]:"";j(n,s,i,l)||(r+=1);}return r},G=n=>{let r=0,e=0;for(let s=0;s<n.length;s+=1)n[s]==="*"?e+=1:(e>=3&&(r+=Math.floor(e/3)),e=0);return e>=3&&(r+=Math.floor(e/3)),r},J=(n,r,e)=>{if(!r||c.test(r))return true;let i=n.substring(0,e).lastIndexOf(`
7
+ `),l=i===-1?0:i+1,t=n.substring(l,e);return k.test(t)&&r.includes(`
8
+ `)?true:h(n,e,"*")},N=n=>{let r=n.match(p);if(!r)return n;let e=r[2],s=n.lastIndexOf(r[1]);return g(n,s)||J(n,e,s)?n:(n.match(/\*\*/g)||[]).length%2===1?`${n}**`:n},V=(n,r,e)=>{if(!r||c.test(r))return true;let i=n.substring(0,e).lastIndexOf(`
9
+ `),l=i===-1?0:i+1,t=n.substring(l,e);return k.test(t)&&r.includes(`
10
+ `)?true:h(n,e,"_")},w=n=>{let r=n.match(I);if(!r)return n;let e=r[2],s=n.lastIndexOf(r[1]);return g(n,s)||V(n,e,s)?n:(n.match(/__/g)||[]).length%2===1?`${n}__`:n},X=n=>{for(let r=0;r<n.length;r+=1)if(n[r]==="*"&&n[r-1]!=="*"&&n[r+1]!=="*"&&n[r-1]!=="\\"&&!d(n,r)){let e=r>0?n[r-1]:"",s=r<n.length-1?n[r+1]:"";if(e&&s&&a(e)&&a(s))continue;return r}return -1},L=n=>{if(!n.match(B))return n;let e=X(n);if(e===-1||g(n,e))return n;let s=n.substring(e+1);return !s||c.test(s)?n:U(n)%2===1?`${n}*`:n},W=n=>{for(let r=0;r<n.length;r+=1)if(n[r]==="_"&&n[r-1]!=="_"&&n[r+1]!=="_"&&n[r-1]!=="\\"&&!d(n,r)&&!b(n,r)){let e=r>0?n[r-1]:"",s=r<n.length-1?n[r+1]:"";if(e&&s&&a(e)&&a(s))continue;return r}return -1},Z=n=>{let r=n.length;for(;r>0&&n[r-1]===`
11
+ `;)r-=1;if(r<n.length){let e=n.slice(0,r),s=n.slice(r);return `${e}_${s}`}return `${n}_`},x=n=>{if(!n.endsWith("**"))return null;let r=n.slice(0,-2);if((r.match(/\*\*/g)||[]).length%2!==1)return null;let s=r.indexOf("**"),i=W(r);return s!==-1&&i!==-1&&s<i?`${r}_**`:null},R=n=>{if(!n.match($))return n;let e=W(n);if(e===-1||g(n,e))return n;let s=n.substring(e+1);if(!s||c.test(s))return n;if(q(n)%2===1){let l=x(n);return l!==null?l:Z(n)}return n},nn=n=>{let r=(n.match(/\*\*/g)||[]).length,e=U(n);return r%2===0&&e%2===0},rn=(n,r,e)=>!r||c.test(r)||g(n,e)?true:h(n,e,"*"),E=n=>{if(_.test(n))return n;let r=n.match(P);if(!r)return n;let e=r[2],s=n.lastIndexOf(r[1]);return rn(n,e,s)?n:G(n)%2===1?nn(n)?n:`${n}***`:n};var m=(n,r)=>{let e=false,s=false;for(let i=0;i<r;i+=1){if(n.substring(i,i+3)==="```"){s=!s,i+=2;continue}!s&&n[i]==="`"&&(e=!e);}return e||s},en=(n,r)=>{let e=n.substring(r,r+3)==="```",s=r>0&&n.substring(r-1,r+2)==="```",i=r>1&&n.substring(r-2,r+1)==="```";return e||s||i},H=n=>{let r=0;for(let e=0;e<n.length;e+=1)n[e]==="`"&&!en(n,e)&&(r+=1);return r};var sn=n=>!n.match(S)||n.includes(`
12
+ `)?null:n.endsWith("``")&&!n.endsWith("```")?`${n}\``:n,tn=n=>(n.match(/```/g)||[]).length%2===1,D=n=>{let r=sn(n);if(r!==null)return r;let e=n.match(M);if(e&&!tn(n)){let s=e[2];if(!s||c.test(s))return n;if(H(n)%2===1)return `${n}\``}return n};var ln=(n,r)=>r>=2&&n.substring(r-2,r+1)==="```"||r>=1&&n.substring(r-1,r+2)==="```"||r<=n.length-3&&n.substring(r,r+3)==="```",on=n=>{let r=0,e=false;for(let s=0;s<n.length-1;s+=1)n[s]==="`"&&!ln(n,s)&&(e=!e),!e&&n[s]==="$"&&n[s+1]==="$"&&(r+=1,s+=1);return r},cn=n=>{let r=n.indexOf("$$");return r!==-1&&n.indexOf(`
13
+ `,r)!==-1&&!n.endsWith(`
14
+ `)?`${n}
15
+ $$`:`${n}$$`},F=n=>on(n)%2===0?n:cn(n);var an=(n,r)=>{if(n.substring(r+2).includes(")"))return null;let s=O(n,r);if(s===-1||m(n,s))return null;let i=s>0&&n[s-1]==="!",l=i?s-1:s,t=n.substring(0,l);if(i)return t;let f=n.substring(s+1,r);return `${t}[${f}](streamdown:incomplete-link)`},un=(n,r)=>{let e=r>0&&n[r-1]==="!",s=e?r-1:r;if(!n.substring(r+1).includes("]")){let t=n.substring(0,s);return e?t:`${n}](streamdown:incomplete-link)`}if(T(n,r)===-1){let t=n.substring(0,s);return e?t:`${n}](streamdown:incomplete-link)`}return null},v=n=>{let r=n.lastIndexOf("](");if(r!==-1&&!m(n,r)){let e=an(n,r);if(e!==null)return e}for(let e=n.length-1;e>=0;e-=1)if(n[e]==="["&&!m(n,e)){let s=un(n,e);if(s!==null)return s}return n};var fn=/^-{1,2}$/,gn=/^[\s]*-{1,2}[\s]+$/,dn=/^={1,2}$/,hn=/^[\s]*={1,2}[\s]+$/,y=n=>{if(!n||typeof n!="string")return n;let r=n.lastIndexOf(`
16
+ `);if(r===-1)return n;let e=n.substring(r+1),s=n.substring(0,r),i=e.trim();if(fn.test(i)&&!e.match(gn)){let t=s.split(`
17
+ `).at(-1);if(t&&t.trim().length>0)return `${n}\u200B`}if(dn.test(i)&&!e.match(hn)){let t=s.split(`
18
+ `).at(-1);if(t&&t.trim().length>0)return `${n}\u200B`}return n};var K=n=>{let r=n.match(A);if(r){let e=r[2];if(!e||c.test(e))return n;if((n.match(/~~/g)||[]).length%2===1)return `${n}~~`}return n};var u=n=>n!==false,mn=(n,r)=>{if(!n||typeof n!="string")return n;let e=n.endsWith(" ")&&!n.endsWith(" ")?n.slice(0,-1):n;if(u(r==null?void 0:r.setextHeadings)&&(e=y(e)),u(r==null?void 0:r.links)||u(r==null?void 0:r.images)){let s=v(e);if(s.endsWith("](streamdown:incomplete-link)"))return s;e=s;}return u(r==null?void 0:r.boldItalic)&&(e=E(e)),u(r==null?void 0:r.bold)&&(e=N(e)),u(r==null?void 0:r.italic)&&(e=w(e),e=L(e),e=R(e)),u(r==null?void 0:r.inlineCode)&&(e=D(e)),u(r==null?void 0:r.strikethrough)&&(e=K(e)),u(r==null?void 0:r.katex)&&(e=F(e)),e},Fn=mn;export{Fn as default};
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@janhq/remend",
3
+ "version": "1.0.1",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "homepage": "https://streamdown.ai/docs/termination",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/janhq/streamdown.git",
23
+ "directory": "packages/remend"
24
+ },
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "test": "vitest run",
28
+ "test:ui": "vitest --ui run",
29
+ "test:coverage": "vitest --coverage run",
30
+ "bench": "vitest bench --run > results.txt",
31
+ "bench:ui": "vitest bench --ui --run"
32
+ },
33
+ "author": "Hayden Bleasel <hayden.bleasel@vercel.com>",
34
+ "license": "Apache-2.0",
35
+ "description": "Self-healing markdown. Intelligently parses and styles incomplete Markdown blocks.",
36
+ "devDependencies": {
37
+ "@vitest/coverage-v8": "^4.0.15",
38
+ "tsup": "^8.5.1",
39
+ "vitest": "^4.0.15"
40
+ }
41
+ }