stream-heal 0.0.0-stage → 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fahad Shaikh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,248 @@
1
- # Temporary Holding Version
1
+ # 🩹 stream-heal
2
+
3
+ > **Zero-dependency, fault-tolerant streaming JSON parser and auto-healer for LLM outputs and generative UI.**
4
+
5
+ [![npm version](https://img.shields.io/npm/v/stream-heal.svg?style=flat-square)](https://www.npmjs.com/package/stream-heal)
6
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/stream-heal?style=flat-square)](https://bundlephobia.com/package/stream-heal)
7
+ [![license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE)
8
+ [![dependencies](https://img.shields.io/badge/dependencies-0-success.svg?style=flat-square)](package.json)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg?style=flat-square)](tsconfig.json)
10
+
11
+ ---
12
+
13
+ ### 💡 The 5-Second Mental Model
14
+
15
+ > **Think of `stream-heal` as `JSON.parse()`, but built for streaming AI.**
16
+ > When an LLM outputs JSON token-by-token, standard `JSON.parse()` crashes on every single chunk with `SyntaxError: Unexpected end of JSON`.
17
+ > `stream-heal` fixes the brackets, strings, and commas on the fly so your app gets a **clean, valid JavaScript object at every single frame**.
18
+
19
+ ---
20
+
21
+ ## ⚡ The Problem
22
+
23
+ When OpenAI, Claude, Gemini, or Ollama streams JSON, each chunk arrives half-finished:
24
+
25
+ ```json
26
+ {"user": {"name": "Alex", "skills": ["TypeScript", "Re
27
+ ```
28
+
29
+ If you call `JSON.parse()` on this, your app crashes:
30
+ ```
31
+ 💥 SyntaxError: Unexpected end of JSON input
32
+ ```
33
+
34
+ Most developers either:
35
+ 1. Wait 5 to 10 seconds for the entire stream to finish (ruining the fast AI experience).
36
+ 2. Write messy regex hacks that freeze the user's browser.
37
+
38
+ ---
39
+
40
+ ## ✨ The Solution
41
+
42
+ `stream-heal` auto-heals incomplete tokens **in 5.5 microseconds** with **0 external dependencies** and a **sub-2kB** footprint.
43
+
44
+ ```ts
45
+ import { parseStreamJson } from "stream-heal";
46
+
47
+ // Incomplete chunk while the AI is still typing:
48
+ const chunk = '{"name": "Alex", "active": tr';
49
+
50
+ const data = parseStreamJson(chunk);
51
+ console.log(data);
52
+ // Output: { name: "Alex", active: true } <-- Valid, safe, never crashes!
53
+ ```
54
+
55
+ ---
56
+
57
+ ## 🛠️ What `stream-heal` Fixes Automatically
58
+
59
+ | What the AI outputs mid-stream | What `stream-heal` produces |
60
+ | :--- | :--- |
61
+ | **Unclosed strings:** `{"title": "AI Roadmap` | `{"title": "AI Roadmap"}` |
62
+ | **Missing quotes in arrays:** `["cricket, "racing"` | `["cricket", "racing"]` |
63
+ | **Markdown wrappers:** ` ```json\n{"id": 1}\n``` ` | `{"id": 1}` |
64
+ | **Partial booleans/null:** `{"flag": tr` | `{"flag": true}` |
65
+ | **Trailing decimals:** `{"price": 49.` | `{"price": 49.0}` |
66
+ | **Leading decimals:** `{"price": .50}` | `{"price": 0.50}` |
67
+ | **Dangling commas:** `{"items": [1, 2, ]` | `{"items": [1, 2]}` |
68
+ | **Missing commas:** `{"a": 1 "b": 2}` | `{"a": 1, "b": 2}` |
69
+ | **Single quotes:** `{'name': 'raj'}` | `{"name": "raj"}` |
70
+ | **Python casing:** `{"active": True, "data": None}` | `{"active": true, "data": null}` |
71
+ | **Unquoted values:** `{"status": active}` | `{"status": "active"}` |
72
+ | **Code & template strings:** `{"code": "if (x) { run(); }` | `{"code": "if (x) { run(); }"}` (preserves braces & regexes without flicker) |
73
+
74
+ ---
75
+
76
+ ## 📦 Installation
77
+
78
+ ```bash
79
+ npm install stream-heal
80
+ # or
81
+ pnpm add stream-heal
82
+ # or
83
+ yarn add stream-heal
84
+ ```
85
+
86
+ ---
87
+
88
+ ## 🚀 Quick Start
89
+
90
+ ### 1. Simple One-Liner Stream Parsing
91
+
92
+ ```ts
93
+ import { parseStreamJson } from "stream-heal";
94
+
95
+ // Incomplete JSON from an LLM stream:
96
+ const chunk = '{"title": "AI Roadmap", "tags": ["agent", "web';
97
+
98
+ const data = parseStreamJson<{ title: string; tags: string[] }>(chunk);
99
+ console.log(data);
100
+ // Output: { title: "AI Roadmap", tags: ["agent", "web"] }
101
+ ```
102
+
103
+ ---
104
+
105
+ ### 2. Stream with OpenAI / Anthropic / Ollama
106
+
107
+ ```ts
108
+ import { healAsyncIterable } from "stream-heal";
109
+ import OpenAI from "openai";
110
+
111
+ const openai = new OpenAI();
112
+
113
+ const stream = await openai.chat.completions.create({
114
+ model: "gpt-4o",
115
+ messages: [{ role: "user", content: "Generate project stats in JSON" }],
116
+ stream: true,
117
+ response_format: { type: "json_object" }
118
+ });
119
+
120
+ async function* tokenGenerator() {
121
+ for await (const chunk of stream) {
122
+ yield chunk.choices[0]?.delta?.content || "";
123
+ }
124
+ }
125
+
126
+ // Consumes the stream token-by-token with zero crashes:
127
+ for await (const partial of healAsyncIterable<ProjectStats>(tokenGenerator())) {
128
+ console.log("Current state:", partial);
129
+ }
130
+ ```
131
+
132
+ ---
133
+
134
+ ### 3. React Hook with Built-in Throttling (`stream-heal/react`)
135
+
136
+ Prevent UI freezes during high-speed streams (e.g., Groq, Cerebras, 100+ tokens/sec) using automatic frame batching:
137
+
138
+ ```tsx
139
+ import React from "react";
140
+ import { useStreamingJson } from "stream-heal/react";
141
+
142
+ interface ProductCard {
143
+ name: string;
144
+ price: number;
145
+ features: string[];
146
+ }
147
+
148
+ export function LiveProductCard() {
149
+ const { data, isLoading, pipe } = useStreamingJson<ProductCard>({
150
+ throttleMs: 30 // Updates smoothly at ~33fps
151
+ });
152
+
153
+ const handleGenerate = async () => {
154
+ const response = await fetch("/api/generate");
155
+ if (response.body) {
156
+ pipe(response.body); // Directly pipe Fetch response stream!
157
+ }
158
+ };
159
+
160
+ return (
161
+ <div>
162
+ <button onClick={handleGenerate} disabled={isLoading}>
163
+ {isLoading ? "Streaming..." : "Generate Product"}
164
+ </button>
165
+
166
+ <h2>{data?.name || "Generating title..."}</h2>
167
+ <p>Price: ${data?.price ?? "--"}</p>
168
+ <ul>
169
+ {data?.features?.map((f, i) => (
170
+ <li key={i}>{f}</li>
171
+ ))}
172
+ </ul>
173
+ </div>
174
+ );
175
+ }
176
+ ```
177
+
178
+ ---
179
+
180
+ ### 4. Web Standard TransformStream (Edge / Cloudflare / Node)
181
+
182
+ ```ts
183
+ import { createHealTransformStream } from "stream-heal";
184
+
185
+ const response = await fetch("https://api.example.com/llm-stream");
186
+
187
+ const healedJsonStream = response.body
188
+ .pipeThrough(new TextDecoderStream())
189
+ .pipeThrough(createHealTransformStream<MyData>());
190
+ ```
191
+
192
+ ---
193
+
194
+ ## 📊 Real Benchmarks
195
+
196
+ Benchmarked on **50,000 partial chunks** simulating token-by-token LLM generation:
197
+
198
+ | Metric | Result |
199
+ | :--- | :--- |
200
+ | **Average Latency** | **5.56 µs** (microseconds!) per chunk |
201
+ | **Throughput** | **~180,000 ops / sec** |
202
+ | **Total Test Operations** | 50,000 operations in 278 ms |
203
+ | **Crashes / Errors** | **0** (100% fault-tolerant) |
204
+ | **Bundle Size** | **< 2 kB** minzipped |
205
+ | **Dependencies** | **0** |
206
+
207
+ ---
208
+
209
+ ## 🛠️ API Reference
210
+
211
+ ### `parseStreamJson<T>(input: string, options?: ParseOptions): Partial<T> | null`
212
+ Parses and auto-heals an incomplete JSON string into a strongly-typed partial object.
213
+
214
+ ### `repairJson(input: string, options?: RepairOptions): string`
215
+ Returns the repaired raw JSON string ready for `JSON.parse()`.
216
+
217
+ ### `createStreamHealer<T>(options?: StreamHealerOptions): StreamHealer<T>`
218
+ Creates an incremental, stateful stream tracker.
219
+
220
+ ### `useStreamingJson<T>(options?: UseStreamingJsonOptions<T>)`
221
+ React hook providing `{ data, isLoading, isComplete, pipe, feed, reset }`.
222
+
223
+ ---
224
+
225
+ ## 🤝 Contributing
226
+
227
+ Contributions, issues, and feature requests are welcome!
228
+
229
+ ```bash
230
+ # Clone the repository
231
+ git clone https://github.com/Fahad6160/stream-heal.git
232
+
233
+ # Install dependencies
234
+ npm install
235
+
236
+ # Run test suite
237
+ npm test
238
+
239
+ # Run benchmark
240
+ npm run bench
241
+ ```
242
+
243
+ ---
244
+
245
+ ## 📄 License
246
+
247
+ MIT © 2026 [Fahad Shaikh](https://github.com/Fahad6160). Built for the modern AI web developer.
2
248
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
@@ -0,0 +1,121 @@
1
+ /**
2
+ * stream-heal: Zero-dependency, fault-tolerant streaming JSON parser and auto-healer
3
+ * Production-hardened against all real-world LLM streaming edge cases.
4
+ */
5
+ interface ParseOptions {
6
+ /**
7
+ * If true, automatically strips markdown code fences (e.g. ```json ... ```)
8
+ * and ignores conversational preambles/postambles from LLMs.
9
+ * @default true
10
+ */
11
+ stripMarkdown?: boolean;
12
+ /**
13
+ * Fallback value if the JSON cannot be parsed at all yet (e.g., empty stream).
14
+ * @default null
15
+ */
16
+ fallback?: unknown;
17
+ /**
18
+ * If true, throws a SyntaxError if healing fails, instead of returning the fallback.
19
+ * @default false
20
+ */
21
+ strict?: boolean;
22
+ }
23
+ interface RepairOptions {
24
+ /**
25
+ * Strip markdown wrappers like ```json ... ```
26
+ * @default true
27
+ */
28
+ stripMarkdown?: boolean;
29
+ }
30
+ /**
31
+ * Extracts and sanitizes the core JSON text from an LLM response stream,
32
+ * stripping markdown codeblock indicators and conversational fluff.
33
+ */
34
+ declare function extractJsonCandidate(text: string, stripMarkdown?: boolean): string;
35
+ /**
36
+ * Repairs a truncated or malformed JSON string on the fly, producing a valid JSON string
37
+ * that can be safely parsed by native JSON.parse().
38
+ */
39
+ declare function repairJson(input: string, options?: RepairOptions): string;
40
+ /**
41
+ * Parses a streaming or partial JSON string into a strongly-typed JavaScript object.
42
+ * Safe against incomplete chunks, never crashes, auto-repairs truncated streams.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * const partial = parseStreamJson<{ name: string; age: number }>('{"name": "Alex", "age": 2');
47
+ * console.log(partial); // { name: "Alex", age: 2 }
48
+ * ```
49
+ */
50
+ declare function parseStreamJson<T = unknown>(input: string, options?: ParseOptions): Partial<T> | null;
51
+
52
+ interface StreamHealerOptions extends ParseOptions {
53
+ /**
54
+ * Callback fired whenever new valid partial JSON state is resolved.
55
+ */
56
+ onUpdate?: (partial: unknown) => void;
57
+ /**
58
+ * Callback fired if an unrecoverable syntax error occurs in strict mode.
59
+ */
60
+ onError?: (error: Error) => void;
61
+ }
62
+ /**
63
+ * Stateful streaming controller for incrementally receiving chunks from LLMs.
64
+ */
65
+ declare class StreamHealer<T = unknown> {
66
+ private buffer;
67
+ private lastParsed;
68
+ private isCompleted;
69
+ private options;
70
+ constructor(options?: StreamHealerOptions);
71
+ /**
72
+ * Appends an incremental text chunk to the stream and returns the latest repaired partial object.
73
+ */
74
+ push(chunk: string): Partial<T> | null;
75
+ /**
76
+ * Signals that the stream has finished. Returns the final parsed object.
77
+ */
78
+ finish(): Partial<T> | null;
79
+ /**
80
+ * Returns the raw accumulated string buffer so far.
81
+ */
82
+ getRawBuffer(): string;
83
+ /**
84
+ * Returns the latest successfully repaired partial object.
85
+ */
86
+ getLatest(): Partial<T> | null;
87
+ /**
88
+ * Resets the healer state for a new stream.
89
+ */
90
+ reset(): void;
91
+ }
92
+ /**
93
+ * Creates a stateful StreamHealer instance.
94
+ */
95
+ declare function createStreamHealer<T = unknown>(options?: StreamHealerOptions): StreamHealer<T>;
96
+ /**
97
+ * Web Standard TransformStream compatible with Fetch API response.body.
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * const response = await fetch('/api/llm-stream');
102
+ * const jsonStream = response.body
103
+ * .pipeThrough(new TextDecoderStream())
104
+ * .pipeThrough(createHealTransformStream<MySchema>());
105
+ * ```
106
+ */
107
+ declare function createHealTransformStream<T = unknown>(options?: StreamHealerOptions): TransformStream<string, Partial<T>>;
108
+ /**
109
+ * Consumes an AsyncIterable of string chunks (such as from OpenAI SDK, Anthropic, or Ollama)
110
+ * and yields the repaired partial JSON objects in real-time.
111
+ *
112
+ * @example
113
+ * ```ts
114
+ * for await (const user of healAsyncIterable<User>(openaiStream)) {
115
+ * renderCard(user.name, user.bio);
116
+ * }
117
+ * ```
118
+ */
119
+ declare function healAsyncIterable<T = unknown>(stream: AsyncIterable<string>, options?: StreamHealerOptions): AsyncGenerator<Partial<T>, void, unknown>;
120
+
121
+ export { type ParseOptions, type RepairOptions, StreamHealer, type StreamHealerOptions, createHealTransformStream, createStreamHealer, extractJsonCandidate, healAsyncIterable, parseStreamJson, repairJson };
@@ -0,0 +1,121 @@
1
+ /**
2
+ * stream-heal: Zero-dependency, fault-tolerant streaming JSON parser and auto-healer
3
+ * Production-hardened against all real-world LLM streaming edge cases.
4
+ */
5
+ interface ParseOptions {
6
+ /**
7
+ * If true, automatically strips markdown code fences (e.g. ```json ... ```)
8
+ * and ignores conversational preambles/postambles from LLMs.
9
+ * @default true
10
+ */
11
+ stripMarkdown?: boolean;
12
+ /**
13
+ * Fallback value if the JSON cannot be parsed at all yet (e.g., empty stream).
14
+ * @default null
15
+ */
16
+ fallback?: unknown;
17
+ /**
18
+ * If true, throws a SyntaxError if healing fails, instead of returning the fallback.
19
+ * @default false
20
+ */
21
+ strict?: boolean;
22
+ }
23
+ interface RepairOptions {
24
+ /**
25
+ * Strip markdown wrappers like ```json ... ```
26
+ * @default true
27
+ */
28
+ stripMarkdown?: boolean;
29
+ }
30
+ /**
31
+ * Extracts and sanitizes the core JSON text from an LLM response stream,
32
+ * stripping markdown codeblock indicators and conversational fluff.
33
+ */
34
+ declare function extractJsonCandidate(text: string, stripMarkdown?: boolean): string;
35
+ /**
36
+ * Repairs a truncated or malformed JSON string on the fly, producing a valid JSON string
37
+ * that can be safely parsed by native JSON.parse().
38
+ */
39
+ declare function repairJson(input: string, options?: RepairOptions): string;
40
+ /**
41
+ * Parses a streaming or partial JSON string into a strongly-typed JavaScript object.
42
+ * Safe against incomplete chunks, never crashes, auto-repairs truncated streams.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * const partial = parseStreamJson<{ name: string; age: number }>('{"name": "Alex", "age": 2');
47
+ * console.log(partial); // { name: "Alex", age: 2 }
48
+ * ```
49
+ */
50
+ declare function parseStreamJson<T = unknown>(input: string, options?: ParseOptions): Partial<T> | null;
51
+
52
+ interface StreamHealerOptions extends ParseOptions {
53
+ /**
54
+ * Callback fired whenever new valid partial JSON state is resolved.
55
+ */
56
+ onUpdate?: (partial: unknown) => void;
57
+ /**
58
+ * Callback fired if an unrecoverable syntax error occurs in strict mode.
59
+ */
60
+ onError?: (error: Error) => void;
61
+ }
62
+ /**
63
+ * Stateful streaming controller for incrementally receiving chunks from LLMs.
64
+ */
65
+ declare class StreamHealer<T = unknown> {
66
+ private buffer;
67
+ private lastParsed;
68
+ private isCompleted;
69
+ private options;
70
+ constructor(options?: StreamHealerOptions);
71
+ /**
72
+ * Appends an incremental text chunk to the stream and returns the latest repaired partial object.
73
+ */
74
+ push(chunk: string): Partial<T> | null;
75
+ /**
76
+ * Signals that the stream has finished. Returns the final parsed object.
77
+ */
78
+ finish(): Partial<T> | null;
79
+ /**
80
+ * Returns the raw accumulated string buffer so far.
81
+ */
82
+ getRawBuffer(): string;
83
+ /**
84
+ * Returns the latest successfully repaired partial object.
85
+ */
86
+ getLatest(): Partial<T> | null;
87
+ /**
88
+ * Resets the healer state for a new stream.
89
+ */
90
+ reset(): void;
91
+ }
92
+ /**
93
+ * Creates a stateful StreamHealer instance.
94
+ */
95
+ declare function createStreamHealer<T = unknown>(options?: StreamHealerOptions): StreamHealer<T>;
96
+ /**
97
+ * Web Standard TransformStream compatible with Fetch API response.body.
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * const response = await fetch('/api/llm-stream');
102
+ * const jsonStream = response.body
103
+ * .pipeThrough(new TextDecoderStream())
104
+ * .pipeThrough(createHealTransformStream<MySchema>());
105
+ * ```
106
+ */
107
+ declare function createHealTransformStream<T = unknown>(options?: StreamHealerOptions): TransformStream<string, Partial<T>>;
108
+ /**
109
+ * Consumes an AsyncIterable of string chunks (such as from OpenAI SDK, Anthropic, or Ollama)
110
+ * and yields the repaired partial JSON objects in real-time.
111
+ *
112
+ * @example
113
+ * ```ts
114
+ * for await (const user of healAsyncIterable<User>(openaiStream)) {
115
+ * renderCard(user.name, user.bio);
116
+ * }
117
+ * ```
118
+ */
119
+ declare function healAsyncIterable<T = unknown>(stream: AsyncIterable<string>, options?: StreamHealerOptions): AsyncGenerator<Partial<T>, void, unknown>;
120
+
121
+ export { type ParseOptions, type RepairOptions, StreamHealer, type StreamHealerOptions, createHealTransformStream, createStreamHealer, extractJsonCandidate, healAsyncIterable, parseStreamJson, repairJson };
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ 'use strict';function d(o,i=true){if(!o||typeof o!="string")return "";let n=o.trim();if(!i)return n;let t=n.indexOf("```");if(t!==-1){let e=n.indexOf(`
2
+ `,t);if(e!==-1)n=n.slice(e+1);else return "";let h=n.lastIndexOf("```");h!==-1&&(n=n.slice(0,h));}let s=n.indexOf("{"),a=n.indexOf("["),l=-1;return s!==-1&&a!==-1?l=Math.min(s,a):s!==-1?l=s:a!==-1&&(l=a),l===-1?"":n.slice(l).trim()}function m(o){let i="",n=false,t=false,s=o.length;for(let a=0;a<s;a++){let l=o[a];if(n){i+=l,t?t=false:l==="\\"?t=true:l==='"'&&(n=false);continue}if(l==='"'){n=true,i+=l;continue}if(l==="/"&&a+1<s&&o[a+1]==="/"){let e=o.indexOf(`
3
+ `,a+2);if(e===-1)break;a=e,i+=`
4
+ `;continue}if(l==="/"&&a+1<s&&o[a+1]==="*"){let e=o.indexOf("*/",a+2);if(e===-1)break;a=e+1;continue}i+=l;}return i}function b(o){let i="",n=false,t=false,s=false;for(let a=0;a<o.length;a++){let l=o[a];if(s){i+=l,s=false;continue}if(l==="\\"){i+=l,s=true;continue}if(l==='"'&&!t){n=!n,i+=l;continue}if(l==="'"&&!n){t=!t,i+='"';continue}i+=l;}return i}function $(o,i){let n=i?.stripMarkdown??true,t=d(o,n);if(!t)return "";t=m(t),t=b(t),t=t.replace(/([:,\\[]\s*)\.(\d+)/g,"$10.$2"),t=t.replace(/([:,\\[]\s*)\+(\d+)/g,"$1$2"),t=t.replace(/,\s*,+/g,","),t=t.replace(/:\s*,/g,": null,"),t=t.replace(/:\s*([a-zA-Z_$][a-zA-Z0-9_$-]*)\s*(?=([,}\]]|$))/g,(c,r)=>{let f=r.toLowerCase();return f==="true"||f==="false"||f==="null"||r==="None"||r==="NaN"||r==="Infinity"||/^t(r(u(e)?)?)?$/i.test(r)||/^f(a(l(s(e)?)?)?)?$/i.test(r)||/^n(u(l(l)?)?)?$/i.test(r)?`: ${r}`:`: "${r}"`}),t=t.replace(/(:\s*"[^",\n\r]*),\s*("([a-zA-Z0-9_$]+)"\s*:)/g,'$1", $2'),t=t.replace(/(\[\s*(?:(?:"[^"]*",\s*)*))"([^",\n\r]*),\s*"/g,'$1"$2", "'),t=t.replace(/\[\s*"\s*\]/g,'[""]'),t=t.replace(/:\s*"\s*([}\]])/g,': ""$1');let s=[],a=false,l=false,e="",h=t.length;for(let c=0;c<h;c++){let r=t[c];if(a){if(l){e+=r,l=false;continue}if(r==="\\"){l=true,e+=r;continue}if(r==='"'){a=false,e+=r;continue}let f=r.charCodeAt(0);if(f<32){r===`
5
+ `?e+="\\n":r==="\r"?e+="\\r":r===" "?e+="\\t":r==="\b"?e+="\\b":r==="\f"?e+="\\f":e+="\\u"+f.toString(16).padStart(4,"0");continue}e+=r;continue}if(r==='"'){let f=e.trimEnd(),u=f[f.length-1];(u==='"'||u==="}"||u==="]"||u==="e"||u==="l")&&(e=f+", "),a=true,e+=r;continue}if(r==="{"||r==="["){let f=e.trimEnd(),u=f[f.length-1];(u==="}"||u==="]"||u==='"')&&(e=f+", "),s.push(r),e+=r;continue}if(r==="}"){if(s.length>0&&s[s.length-1]==="{"&&(s.pop(),e=e.replace(/,\s*$/,""),e+=r,s.length===0))break;continue}if(r==="]"){if(s.length>0&&s[s.length-1]==="["&&(s.pop(),e=e.replace(/,\s*$/,""),e+=r,s.length===0))break;continue}e+=r;}if(a&&(l&&e.endsWith("\\")&&(e=e.slice(0,-1)),e=e.replace(/\\u[0-9a-fA-F]{0,3}$/,""),e+='"'),e=e.trimEnd(),e=e.replace(/([:,\\[]\s*)True\b/g,"$1true"),e=e.replace(/([:,\\[]\s*)False\b/g,"$1false"),e=e.replace(/([:,\\[]\s*)None\b/g,"$1null"),e=e.replace(/([:,\\[]\s*)NaN\b/g,"$1null"),e=e.replace(/([:,\\[]\s*)Infinity\b/g,"$1null"),e=e.replace(/([:,\\[]\s*)-Infinity\b/g,"$1null"),e=e.replace(/:\s*t(r(u(e)?)?)?$/i,": true"),e=e.replace(/:\s*f(a(l(s(e)?)?)?)?$/i,": false"),e=e.replace(/:\s*n(u(l(l)?)?)?$/i,": null"),e=e.replace(/([,[])\s*t(r(u(e)?)?)?$/i,"$1 true"),e=e.replace(/([,[])\s*f(a(l(s(e)?)?)?)?$/i,"$1 false"),e=e.replace(/([,[])\s*n(u(l(l)?)?)?$/i,"$1 null"),e=e.replace(/:\s*(-?\d+)\.(?=\s*([,}\]]|$))/g,": $1.0"),e=e.replace(/([,[])\s*(-?\d+)\.(?=\s*([,}\]]|$))/g,"$1 $2.0"),e=e.replace(/:\s*(-?\d+(\.\d+)?)e[+-]?(?=\s*([,}\]]|$))/gi,": $1"),e=e.replace(/([,[])\s*(-?\d+(\.\d+)?)e[+-]?(?=\s*([,}\]]|$))/gi,"$1 $2"),e=e.replace(/:\s*-(?=\s*([,}\]]|$))/g,": 0"),e=e.replace(/([,[])\s*-(?=\s*([,}\]]|$))/g,"$1 0"),/:\s*$/.test(e)&&(e+="null"),e=e.replace(/,\s*$/,""),e=e.replace(/([{\\,]\s*)([a-zA-Z_$][a-zA-Z0-9_$-]*)\s*:/g,'$1"$2":'),s.length>0&&s[s.length-1]==="{"){let c=e.match(/,\s*"(?:[^"\\]|\\.)*"$/)||e.match(/\{\s*"(?:[^"\\]|\\.)*"$/);if(c){let r=c[0];r.startsWith("{")?e=e.slice(0,e.length-r.length+1):e=e.slice(0,e.length-r.length);}}for(e=e.replace(/,\s*$/,"").trimEnd();s.length>0;){let c=s.pop();e=e.replace(/,\s*$/,"").trimEnd(),c==="{"?e+="}":c==="["&&(e+="]");}return e}function g(o,i){let n=i?.fallback??null;if(!o||typeof o!="string")return n;let t=$(o,{stripMarkdown:i?.stripMarkdown??true});if(!t)return n;try{return JSON.parse(t)}catch(s){if(i?.strict)throw s;return n}}var p=class{buffer="";lastParsed=null;isCompleted=false;options;constructor(i={}){this.options=i,this.lastParsed=i.fallback??null;}push(i){if(this.isCompleted)return this.lastParsed;this.buffer+=i;let n=g(this.buffer,this.options);return n!==null&&(this.lastParsed=n,this.options.onUpdate?.(n)),this.lastParsed}finish(){return this.isCompleted=true,this.lastParsed}getRawBuffer(){return this.buffer}getLatest(){return this.lastParsed}reset(){this.buffer="",this.lastParsed=this.options.fallback??null,this.isCompleted=false;}};function S(o){return new p(o)}function T(o){let i=new p(o);return new TransformStream({transform(n,t){let s=i.push(n);s!==null&&t.enqueue(s);},flush(n){let t=i.finish();t!==null&&n.enqueue(t);}})}async function*k(o,i){let n=new p(i);for await(let s of o){let a=n.push(s);a!==null&&(yield a);}let t=n.finish();t!==null&&(yield t);}exports.StreamHealer=p;exports.createHealTransformStream=T;exports.createStreamHealer=S;exports.extractJsonCandidate=d;exports.healAsyncIterable=k;exports.parseStreamJson=g;exports.repairJson=$;//# sourceMappingURL=index.js.map
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/parser.ts","../src/core/stream.ts"],"names":["extractJsonCandidate","text","stripMarkdown","cleaned","fenceStart","newlineAfterFence","fenceEnd","firstBrace","firstBracket","startIndex","stripComments","input","result","inString","isEscaped","len","i","char","nextNewline","endComment","normalizeSingleQuotes","inDouble","inSingle","repairJson","options","raw","_m","word","lower","stack","code","trimmedSoFar","lastChar","trailingKeyMatch","matchStr","openChar","parseStreamJson","fallback","repaired","error","StreamHealer","chunk","parsed","createStreamHealer","createHealTransformStream","healer","controller","finalResult","healAsyncIterable","stream","partial","finalObj"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * stream-heal: Zero-dependency, fault-tolerant streaming JSON parser and auto-healer\n * Production-hardened against all real-world LLM streaming edge cases.\n */\n\nexport interface ParseOptions {\n /**\n * If true, automatically strips markdown code fences (e.g. ```json ... ```)\n * and ignores conversational preambles/postambles from LLMs.\n * @default true\n */\n stripMarkdown?: boolean;\n\n /**\n * Fallback value if the JSON cannot be parsed at all yet (e.g., empty stream).\n * @default null\n */\n fallback?: unknown;\n\n /**\n * If true, throws a SyntaxError if healing fails, instead of returning the fallback.\n * @default false\n */\n strict?: boolean;\n}\n\nexport interface RepairOptions {\n /**\n * Strip markdown wrappers like ```json ... ```\n * @default true\n */\n stripMarkdown?: boolean;\n}\n\n/**\n * Extracts and sanitizes the core JSON text from an LLM response stream,\n * stripping markdown codeblock indicators and conversational fluff.\n */\nexport function extractJsonCandidate(text: string, stripMarkdown = true): string {\n if (!text || typeof text !== \"string\") return \"\";\n let cleaned = text.trim();\n\n if (!stripMarkdown) {\n return cleaned;\n }\n\n // Handle markdown code blocks e.g. ```json \\n { ... } \\n ```\n const fenceStart = cleaned.indexOf(\"```\");\n if (fenceStart !== -1) {\n // Find the end of the opening fence line (e.g. ```json\\n)\n const newlineAfterFence = cleaned.indexOf(\"\\n\", fenceStart);\n if (newlineAfterFence !== -1) {\n cleaned = cleaned.slice(newlineAfterFence + 1);\n } else {\n // Stream just started with ```json and no newline yet\n return \"\";\n }\n\n // If there is a closing fence ```, strip everything from it\n const fenceEnd = cleaned.lastIndexOf(\"```\");\n if (fenceEnd !== -1) {\n cleaned = cleaned.slice(0, fenceEnd);\n }\n }\n\n // Find the first opening bracket '{' or '['\n const firstBrace = cleaned.indexOf(\"{\");\n const firstBracket = cleaned.indexOf(\"[\");\n\n let startIndex = -1;\n if (firstBrace !== -1 && firstBracket !== -1) {\n startIndex = Math.min(firstBrace, firstBracket);\n } else if (firstBrace !== -1) {\n startIndex = firstBrace;\n } else if (firstBracket !== -1) {\n startIndex = firstBracket;\n }\n\n if (startIndex === -1) {\n return \"\";\n }\n\n return cleaned.slice(startIndex).trim();\n}\n\n/**\n * Strips JS/C-style single-line and multi-line comments outside of strings.\n */\nfunction stripComments(input: string): string {\n let result = \"\";\n let inString = false;\n let isEscaped = false;\n const len = input.length;\n\n for (let i = 0; i < len; i++) {\n const char = input[i];\n\n if (inString) {\n result += char;\n if (isEscaped) {\n isEscaped = false;\n } else if (char === \"\\\\\") {\n isEscaped = true;\n } else if (char === '\"') {\n inString = false;\n }\n continue;\n }\n\n if (char === '\"') {\n inString = true;\n result += char;\n continue;\n }\n\n // Single-line comment //\n if (char === \"/\" && i + 1 < len && input[i + 1] === \"/\") {\n const nextNewline = input.indexOf(\"\\n\", i + 2);\n if (nextNewline === -1) {\n break; // Remainder of input is a comment\n }\n i = nextNewline; // Skip to newline\n result += \"\\n\";\n continue;\n }\n\n // Multi-line comment /* ... */\n if (char === \"/\" && i + 1 < len && input[i + 1] === \"*\") {\n const endComment = input.indexOf(\"*/\", i + 2);\n if (endComment === -1) {\n break; // Unclosed block comment at stream tail\n }\n i = endComment + 1; // Skip past */\n continue;\n }\n\n result += char;\n }\n\n return result;\n}\n\n/**\n * Normalizes Python/JS single-quoted JSON (e.g. {'name': 'raj'}) to double quotes,\n * while carefully preserving apostrophes inside double-quoted strings (e.g. \"O'Connor\").\n */\nfunction normalizeSingleQuotes(text: string): string {\n let result = \"\";\n let inDouble = false;\n let inSingle = false;\n let isEscaped = false;\n\n for (let i = 0; i < text.length; i++) {\n const char = text[i];\n if (isEscaped) {\n result += char;\n isEscaped = false;\n continue;\n }\n if (char === \"\\\\\") {\n result += char;\n isEscaped = true;\n continue;\n }\n if (char === '\"' && !inSingle) {\n inDouble = !inDouble;\n result += char;\n continue;\n }\n if (char === \"'\" && !inDouble) {\n inSingle = !inSingle;\n result += '\"';\n continue;\n }\n result += char;\n }\n return result;\n}\n\n/**\n * Repairs a truncated or malformed JSON string on the fly, producing a valid JSON string\n * that can be safely parsed by native JSON.parse().\n */\nexport function repairJson(input: string, options?: RepairOptions): string {\n const stripMarkdown = options?.stripMarkdown ?? true;\n let raw = extractJsonCandidate(input, stripMarkdown);\n\n if (!raw) {\n return \"\";\n }\n\n // Strip comments outside strings\n raw = stripComments(raw);\n\n // Normalize Python-style single quotes e.g. {'name': 'raj'} -> {\"name\": \"raj\"}\n raw = normalizeSingleQuotes(raw);\n\n // Fix numbers with leading dots e.g. {\"price\": .50} -> {\"price\": 0.50}\n raw = raw.replace(/([:,\\\\[]\\s*)\\.(\\d+)/g, \"$10.$2\");\n\n // Fix numbers with leading plus e.g. {\"count\": +12} -> {\"count\": 12}\n raw = raw.replace(/([:,\\\\[]\\s*)\\+(\\d+)/g, \"$1$2\");\n\n // Fix consecutive duplicate commas e.g. [1,, 2] -> [1, 2]\n raw = raw.replace(/,\\s*,+/g, \",\");\n\n // Fix empty colon before comma e.g. {\"a\": , \"b\": 1} -> {\"a\": null, \"b\": 1}\n raw = raw.replace(/:\\s*,/g, \": null,\");\n\n // Fix unquoted string values e.g. {\"status\": active, \"role\": admin} -> {\"status\": \"active\", \"role\": \"admin\"}\n raw = raw.replace(\n /:\\s*([a-zA-Z_$][a-zA-Z0-9_$-]*)\\s*(?=([,}\\]]|$))/g,\n (_m, word) => {\n const lower = word.toLowerCase();\n if (\n lower === \"true\" ||\n lower === \"false\" ||\n lower === \"null\" ||\n word === \"None\" ||\n word === \"NaN\" ||\n word === \"Infinity\" ||\n /^t(r(u(e)?)?)?$/i.test(word) ||\n /^f(a(l(s(e)?)?)?)?$/i.test(word) ||\n /^n(u(l(l)?)?)?$/i.test(word)\n ) {\n return `: ${word}`;\n }\n return `: \"${word}\"`;\n }\n );\n\n // Fix unclosed quotes before comma and next key e.g. {\"name\":\"raj, \"age\": 25} -> {\"name\":\"raj\", \"age\": 25}\n raw = raw.replace(/(:\\s*\"[^\",\\n\\r]*),\\s*(\"([a-zA-Z0-9_$]+)\"\\s*:)/g, '$1\", $2');\n\n // Fix missing closing quotes in arrays e.g. [\"cricket, \"racing\"] -> [\"cricket\", \"racing\"]\n raw = raw.replace(/(\\[\\s*(?:(?:\"[^\"]*\",\\s*)*))\"([^\",\\n\\r]*),\\s*\"/g, '$1\"$2\", \"');\n\n // Fix stray quotes immediately preceding container closing e.g. [\"] -> [\"\"] or {\"key\": \"} -> {\"key\": \"\"}\n raw = raw.replace(/\\[\\s*\"\\s*\\]/g, '[\"\"]');\n raw = raw.replace(/:\\s*\"\\s*([}\\]])/g, ': \"\"$1');\n\n const stack: Array<\"{\" | \"[\"> = [];\n let inString = false;\n let isEscaped = false;\n let result = \"\";\n const len = raw.length;\n\n for (let i = 0; i < len; i++) {\n const char = raw[i];\n\n if (inString) {\n if (isEscaped) {\n result += char;\n isEscaped = false;\n continue;\n }\n\n if (char === \"\\\\\") {\n isEscaped = true;\n result += char;\n continue;\n }\n\n if (char === '\"') {\n inString = false;\n result += char;\n continue;\n }\n\n // Handle all unescaped control characters in strings (ASCII < 32, forbidden by JSON spec)\n const code = char.charCodeAt(0);\n if (code < 32) {\n if (char === \"\\n\") {\n result += \"\\\\n\";\n } else if (char === \"\\r\") {\n result += \"\\\\r\";\n } else if (char === \"\\t\") {\n result += \"\\\\t\";\n } else if (char === \"\\b\") {\n result += \"\\\\b\";\n } else if (char === \"\\f\") {\n result += \"\\\\f\";\n } else {\n result += \"\\\\u\" + code.toString(16).padStart(4, \"0\");\n }\n continue;\n }\n\n result += char;\n continue;\n }\n\n // --- NOT IN STRING ---\n\n if (char === '\"') {\n // Check for missing comma before this string key/value if preceded by another value\n const trimmedSoFar = result.trimEnd();\n const lastChar = trimmedSoFar[trimmedSoFar.length - 1];\n if (\n lastChar === '\"' ||\n lastChar === \"}\" ||\n lastChar === \"]\" ||\n lastChar === \"e\" || // from true\n lastChar === \"l\" // from null\n ) {\n result = trimmedSoFar + \", \";\n }\n\n inString = true;\n result += char;\n continue;\n }\n\n if (char === \"{\" || char === \"[\") {\n // Check for missing comma before nested container\n const trimmedSoFar = result.trimEnd();\n const lastChar = trimmedSoFar[trimmedSoFar.length - 1];\n if (lastChar === \"}\" || lastChar === \"]\" || lastChar === '\"') {\n result = trimmedSoFar + \", \";\n }\n\n stack.push(char);\n result += char;\n continue;\n }\n\n if (char === \"}\") {\n if (stack.length > 0 && stack[stack.length - 1] === \"{\") {\n stack.pop();\n result = result.replace(/,\\s*$/, \"\");\n result += char;\n // If the root container has closed, stop to ignore trailing conversational postambles\n if (stack.length === 0) {\n break;\n }\n }\n continue;\n }\n\n if (char === \"]\") {\n if (stack.length > 0 && stack[stack.length - 1] === \"[\") {\n stack.pop();\n result = result.replace(/,\\s*$/, \"\");\n result += char;\n // If the root container has closed, stop to ignore trailing conversational postambles\n if (stack.length === 0) {\n break;\n }\n }\n continue;\n }\n\n result += char;\n }\n\n // --- STREAM REPAIR HEURISTICS ---\n\n // 1. If currently inside a string\n if (inString) {\n if (isEscaped) {\n // Remove trailing dangling backslash e.g. \"path\\\": \\\"C:\\\\\n if (result.endsWith(\"\\\\\")) {\n result = result.slice(0, -1);\n }\n }\n // Handle incomplete Unicode escape sequences at end of string e.g. \\u, \\u0, \\u00, \\u004\n result = result.replace(/\\\\u[0-9a-fA-F]{0,3}$/, \"\");\n\n // Close the unclosed string (preserves literal content like code brackets, regexes, math)\n result += '\"';\n }\n\n // 2. Trim trailing whitespaces\n result = result.trimEnd();\n\n // 3. Normalize Python-style constants emitted by models: True, False, None, NaN, Infinity\n result = result.replace(/([:,\\\\[]\\s*)True\\b/g, \"$1true\");\n result = result.replace(/([:,\\\\[]\\s*)False\\b/g, \"$1false\");\n result = result.replace(/([:,\\\\[]\\s*)None\\b/g, \"$1null\");\n result = result.replace(/([:,\\\\[]\\s*)NaN\\b/g, \"$1null\");\n result = result.replace(/([:,\\\\[]\\s*)Infinity\\b/g, \"$1null\");\n result = result.replace(/([:,\\\\[]\\s*)-Infinity\\b/g, \"$1null\");\n\n // 4. Fix incomplete primitives at the end of the stream\n // Object values:\n result = result.replace(/:\\s*t(r(u(e)?)?)?$/i, \": true\");\n result = result.replace(/:\\s*f(a(l(s(e)?)?)?)?$/i, \": false\");\n result = result.replace(/:\\s*n(u(l(l)?)?)?$/i, \": null\");\n\n // Array items:\n result = result.replace(/([,[])\\s*t(r(u(e)?)?)?$/i, \"$1 true\");\n result = result.replace(/([,[])\\s*f(a(l(s(e)?)?)?)?$/i, \"$1 false\");\n result = result.replace(/([,[])\\s*n(u(l(l)?)?)?$/i, \"$1 null\");\n\n // Fix incomplete numbers:\n // Decimals ending with dot e.g. \"price\": 12. -> 12.0\n result = result.replace(/:\\s*(-?\\d+)\\.(?=\\s*([,}\\]]|$))/g, \": $1.0\");\n result = result.replace(/([,[])\\s*(-?\\d+)\\.(?=\\s*([,}\\]]|$))/g, \"$1 $2.0\");\n\n // Exponents in flight e.g. 1.2e, 1.2e+, 1.2e-\n result = result.replace(/:\\s*(-?\\d+(\\.\\d+)?)e[+-]?(?=\\s*([,}\\]]|$))/gi, \": $1\");\n result = result.replace(/([,[])\\s*(-?\\d+(\\.\\d+)?)e[+-]?(?=\\s*([,}\\]]|$))/gi, \"$1 $2\");\n\n // Lone minus signs e.g. \"count\": - -> 0\n result = result.replace(/:\\s*-(?=\\s*([,}\\]]|$))/g, \": 0\");\n result = result.replace(/([,[])\\s*-(?=\\s*([,}\\]]|$))/g, \"$1 0\");\n\n // 5. Clean trailing unfulfilled tokens\n // If result ends with a colon e.g. `{\"key\": `\n if (/:\\s*$/.test(result)) {\n result += \"null\";\n }\n\n // If result ends with a trailing comma e.g. `[1, 2, ` or `{\"a\": 1, `\n result = result.replace(/,\\s*$/, \"\");\n\n // Auto-quote unquoted keys emitted by open-source models e.g. {name: \"Alex\", count: 10}\n result = result.replace(/([{\\\\,]\\s*)([a-zA-Z_$][a-zA-Z0-9_$-]*)\\s*:/g, '$1\"$2\":');\n\n // If an object ended with an unfinished key without colon (e.g. `{\"key\"` or `{\"key\\\"name\"`)\n if (stack.length > 0 && stack[stack.length - 1] === \"{\") {\n const trailingKeyMatch =\n result.match(/,\\s*\"(?:[^\"\\\\]|\\\\.)*\"$/) ||\n result.match(/\\{\\s*\"(?:[^\"\\\\]|\\\\.)*\"$/);\n if (trailingKeyMatch) {\n const matchStr = trailingKeyMatch[0];\n if (matchStr.startsWith(\"{\")) {\n result = result.slice(0, result.length - matchStr.length + 1); // keep {\n } else {\n result = result.slice(0, result.length - matchStr.length);\n }\n }\n }\n\n // Re-clean trailing commas\n result = result.replace(/,\\s*$/, \"\").trimEnd();\n\n // 6. Close all unclosed brackets/braces in reverse order\n while (stack.length > 0) {\n const openChar = stack.pop();\n result = result.replace(/,\\s*$/, \"\").trimEnd();\n if (openChar === \"{\") {\n result += \"}\";\n } else if (openChar === \"[\") {\n result += \"]\";\n }\n }\n\n return result;\n}\n\n/**\n * Parses a streaming or partial JSON string into a strongly-typed JavaScript object.\n * Safe against incomplete chunks, never crashes, auto-repairs truncated streams.\n *\n * @example\n * ```ts\n * const partial = parseStreamJson<{ name: string; age: number }>('{\"name\": \"Alex\", \"age\": 2');\n * console.log(partial); // { name: \"Alex\", age: 2 }\n * ```\n */\nexport function parseStreamJson<T = unknown>(\n input: string,\n options?: ParseOptions\n): Partial<T> | null {\n const fallback = (options?.fallback ?? null) as Partial<T> | null;\n\n if (!input || typeof input !== \"string\") {\n return fallback;\n }\n\n const repaired = repairJson(input, {\n stripMarkdown: options?.stripMarkdown ?? true\n });\n\n if (!repaired) {\n return fallback;\n }\n\n try {\n return JSON.parse(repaired) as Partial<T>;\n } catch (error) {\n if (options?.strict) {\n throw error;\n }\n return fallback;\n }\n}\n","import { parseStreamJson, ParseOptions } from \"./parser\";\n\nexport interface StreamHealerOptions extends ParseOptions {\n /**\n * Callback fired whenever new valid partial JSON state is resolved.\n */\n onUpdate?: (partial: unknown) => void;\n /**\n * Callback fired if an unrecoverable syntax error occurs in strict mode.\n */\n onError?: (error: Error) => void;\n}\n\n/**\n * Stateful streaming controller for incrementally receiving chunks from LLMs.\n */\nexport class StreamHealer<T = unknown> {\n private buffer = \"\";\n private lastParsed: Partial<T> | null = null;\n private isCompleted = false;\n private options: StreamHealerOptions;\n\n constructor(options: StreamHealerOptions = {}) {\n this.options = options;\n this.lastParsed = (options.fallback ?? null) as Partial<T> | null;\n }\n\n /**\n * Appends an incremental text chunk to the stream and returns the latest repaired partial object.\n */\n public push(chunk: string): Partial<T> | null {\n if (this.isCompleted) {\n return this.lastParsed;\n }\n\n this.buffer += chunk;\n const parsed = parseStreamJson<T>(this.buffer, this.options);\n\n if (parsed !== null) {\n this.lastParsed = parsed;\n this.options.onUpdate?.(parsed);\n }\n\n return this.lastParsed;\n }\n\n /**\n * Signals that the stream has finished. Returns the final parsed object.\n */\n public finish(): Partial<T> | null {\n this.isCompleted = true;\n return this.lastParsed;\n }\n\n /**\n * Returns the raw accumulated string buffer so far.\n */\n public getRawBuffer(): string {\n return this.buffer;\n }\n\n /**\n * Returns the latest successfully repaired partial object.\n */\n public getLatest(): Partial<T> | null {\n return this.lastParsed;\n }\n\n /**\n * Resets the healer state for a new stream.\n */\n public reset(): void {\n this.buffer = \"\";\n this.lastParsed = (this.options.fallback ?? null) as Partial<T> | null;\n this.isCompleted = false;\n }\n}\n\n/**\n * Creates a stateful StreamHealer instance.\n */\nexport function createStreamHealer<T = unknown>(options?: StreamHealerOptions): StreamHealer<T> {\n return new StreamHealer<T>(options);\n}\n\n/**\n * Web Standard TransformStream compatible with Fetch API response.body.\n *\n * @example\n * ```ts\n * const response = await fetch('/api/llm-stream');\n * const jsonStream = response.body\n * .pipeThrough(new TextDecoderStream())\n * .pipeThrough(createHealTransformStream<MySchema>());\n * ```\n */\nexport function createHealTransformStream<T = unknown>(\n options?: StreamHealerOptions\n): TransformStream<string, Partial<T>> {\n const healer = new StreamHealer<T>(options);\n\n return new TransformStream<string, Partial<T>>({\n transform(chunk, controller) {\n const repaired = healer.push(chunk);\n if (repaired !== null) {\n controller.enqueue(repaired);\n }\n },\n flush(controller) {\n const finalResult = healer.finish();\n if (finalResult !== null) {\n controller.enqueue(finalResult);\n }\n }\n });\n}\n\n/**\n * Consumes an AsyncIterable of string chunks (such as from OpenAI SDK, Anthropic, or Ollama)\n * and yields the repaired partial JSON objects in real-time.\n *\n * @example\n * ```ts\n * for await (const user of healAsyncIterable<User>(openaiStream)) {\n * renderCard(user.name, user.bio);\n * }\n * ```\n */\nexport async function* healAsyncIterable<T = unknown>(\n stream: AsyncIterable<string>,\n options?: StreamHealerOptions\n): AsyncGenerator<Partial<T>, void, unknown> {\n const healer = new StreamHealer<T>(options);\n\n for await (const chunk of stream) {\n const partial = healer.push(chunk);\n if (partial !== null) {\n yield partial;\n }\n }\n\n const finalObj = healer.finish();\n if (finalObj !== null) {\n yield finalObj;\n }\n}\n\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,6 @@
1
+ function d(o,i=true){if(!o||typeof o!="string")return "";let n=o.trim();if(!i)return n;let t=n.indexOf("```");if(t!==-1){let e=n.indexOf(`
2
+ `,t);if(e!==-1)n=n.slice(e+1);else return "";let h=n.lastIndexOf("```");h!==-1&&(n=n.slice(0,h));}let s=n.indexOf("{"),a=n.indexOf("["),l=-1;return s!==-1&&a!==-1?l=Math.min(s,a):s!==-1?l=s:a!==-1&&(l=a),l===-1?"":n.slice(l).trim()}function m(o){let i="",n=false,t=false,s=o.length;for(let a=0;a<s;a++){let l=o[a];if(n){i+=l,t?t=false:l==="\\"?t=true:l==='"'&&(n=false);continue}if(l==='"'){n=true,i+=l;continue}if(l==="/"&&a+1<s&&o[a+1]==="/"){let e=o.indexOf(`
3
+ `,a+2);if(e===-1)break;a=e,i+=`
4
+ `;continue}if(l==="/"&&a+1<s&&o[a+1]==="*"){let e=o.indexOf("*/",a+2);if(e===-1)break;a=e+1;continue}i+=l;}return i}function b(o){let i="",n=false,t=false,s=false;for(let a=0;a<o.length;a++){let l=o[a];if(s){i+=l,s=false;continue}if(l==="\\"){i+=l,s=true;continue}if(l==='"'&&!t){n=!n,i+=l;continue}if(l==="'"&&!n){t=!t,i+='"';continue}i+=l;}return i}function $(o,i){let n=i?.stripMarkdown??true,t=d(o,n);if(!t)return "";t=m(t),t=b(t),t=t.replace(/([:,\\[]\s*)\.(\d+)/g,"$10.$2"),t=t.replace(/([:,\\[]\s*)\+(\d+)/g,"$1$2"),t=t.replace(/,\s*,+/g,","),t=t.replace(/:\s*,/g,": null,"),t=t.replace(/:\s*([a-zA-Z_$][a-zA-Z0-9_$-]*)\s*(?=([,}\]]|$))/g,(c,r)=>{let f=r.toLowerCase();return f==="true"||f==="false"||f==="null"||r==="None"||r==="NaN"||r==="Infinity"||/^t(r(u(e)?)?)?$/i.test(r)||/^f(a(l(s(e)?)?)?)?$/i.test(r)||/^n(u(l(l)?)?)?$/i.test(r)?`: ${r}`:`: "${r}"`}),t=t.replace(/(:\s*"[^",\n\r]*),\s*("([a-zA-Z0-9_$]+)"\s*:)/g,'$1", $2'),t=t.replace(/(\[\s*(?:(?:"[^"]*",\s*)*))"([^",\n\r]*),\s*"/g,'$1"$2", "'),t=t.replace(/\[\s*"\s*\]/g,'[""]'),t=t.replace(/:\s*"\s*([}\]])/g,': ""$1');let s=[],a=false,l=false,e="",h=t.length;for(let c=0;c<h;c++){let r=t[c];if(a){if(l){e+=r,l=false;continue}if(r==="\\"){l=true,e+=r;continue}if(r==='"'){a=false,e+=r;continue}let f=r.charCodeAt(0);if(f<32){r===`
5
+ `?e+="\\n":r==="\r"?e+="\\r":r===" "?e+="\\t":r==="\b"?e+="\\b":r==="\f"?e+="\\f":e+="\\u"+f.toString(16).padStart(4,"0");continue}e+=r;continue}if(r==='"'){let f=e.trimEnd(),u=f[f.length-1];(u==='"'||u==="}"||u==="]"||u==="e"||u==="l")&&(e=f+", "),a=true,e+=r;continue}if(r==="{"||r==="["){let f=e.trimEnd(),u=f[f.length-1];(u==="}"||u==="]"||u==='"')&&(e=f+", "),s.push(r),e+=r;continue}if(r==="}"){if(s.length>0&&s[s.length-1]==="{"&&(s.pop(),e=e.replace(/,\s*$/,""),e+=r,s.length===0))break;continue}if(r==="]"){if(s.length>0&&s[s.length-1]==="["&&(s.pop(),e=e.replace(/,\s*$/,""),e+=r,s.length===0))break;continue}e+=r;}if(a&&(l&&e.endsWith("\\")&&(e=e.slice(0,-1)),e=e.replace(/\\u[0-9a-fA-F]{0,3}$/,""),e+='"'),e=e.trimEnd(),e=e.replace(/([:,\\[]\s*)True\b/g,"$1true"),e=e.replace(/([:,\\[]\s*)False\b/g,"$1false"),e=e.replace(/([:,\\[]\s*)None\b/g,"$1null"),e=e.replace(/([:,\\[]\s*)NaN\b/g,"$1null"),e=e.replace(/([:,\\[]\s*)Infinity\b/g,"$1null"),e=e.replace(/([:,\\[]\s*)-Infinity\b/g,"$1null"),e=e.replace(/:\s*t(r(u(e)?)?)?$/i,": true"),e=e.replace(/:\s*f(a(l(s(e)?)?)?)?$/i,": false"),e=e.replace(/:\s*n(u(l(l)?)?)?$/i,": null"),e=e.replace(/([,[])\s*t(r(u(e)?)?)?$/i,"$1 true"),e=e.replace(/([,[])\s*f(a(l(s(e)?)?)?)?$/i,"$1 false"),e=e.replace(/([,[])\s*n(u(l(l)?)?)?$/i,"$1 null"),e=e.replace(/:\s*(-?\d+)\.(?=\s*([,}\]]|$))/g,": $1.0"),e=e.replace(/([,[])\s*(-?\d+)\.(?=\s*([,}\]]|$))/g,"$1 $2.0"),e=e.replace(/:\s*(-?\d+(\.\d+)?)e[+-]?(?=\s*([,}\]]|$))/gi,": $1"),e=e.replace(/([,[])\s*(-?\d+(\.\d+)?)e[+-]?(?=\s*([,}\]]|$))/gi,"$1 $2"),e=e.replace(/:\s*-(?=\s*([,}\]]|$))/g,": 0"),e=e.replace(/([,[])\s*-(?=\s*([,}\]]|$))/g,"$1 0"),/:\s*$/.test(e)&&(e+="null"),e=e.replace(/,\s*$/,""),e=e.replace(/([{\\,]\s*)([a-zA-Z_$][a-zA-Z0-9_$-]*)\s*:/g,'$1"$2":'),s.length>0&&s[s.length-1]==="{"){let c=e.match(/,\s*"(?:[^"\\]|\\.)*"$/)||e.match(/\{\s*"(?:[^"\\]|\\.)*"$/);if(c){let r=c[0];r.startsWith("{")?e=e.slice(0,e.length-r.length+1):e=e.slice(0,e.length-r.length);}}for(e=e.replace(/,\s*$/,"").trimEnd();s.length>0;){let c=s.pop();e=e.replace(/,\s*$/,"").trimEnd(),c==="{"?e+="}":c==="["&&(e+="]");}return e}function g(o,i){let n=i?.fallback??null;if(!o||typeof o!="string")return n;let t=$(o,{stripMarkdown:i?.stripMarkdown??true});if(!t)return n;try{return JSON.parse(t)}catch(s){if(i?.strict)throw s;return n}}var p=class{buffer="";lastParsed=null;isCompleted=false;options;constructor(i={}){this.options=i,this.lastParsed=i.fallback??null;}push(i){if(this.isCompleted)return this.lastParsed;this.buffer+=i;let n=g(this.buffer,this.options);return n!==null&&(this.lastParsed=n,this.options.onUpdate?.(n)),this.lastParsed}finish(){return this.isCompleted=true,this.lastParsed}getRawBuffer(){return this.buffer}getLatest(){return this.lastParsed}reset(){this.buffer="",this.lastParsed=this.options.fallback??null,this.isCompleted=false;}};function S(o){return new p(o)}function T(o){let i=new p(o);return new TransformStream({transform(n,t){let s=i.push(n);s!==null&&t.enqueue(s);},flush(n){let t=i.finish();t!==null&&n.enqueue(t);}})}async function*k(o,i){let n=new p(i);for await(let s of o){let a=n.push(s);a!==null&&(yield a);}let t=n.finish();t!==null&&(yield t);}export{p as StreamHealer,T as createHealTransformStream,S as createStreamHealer,d as extractJsonCandidate,k as healAsyncIterable,g as parseStreamJson,$ as repairJson};//# sourceMappingURL=index.mjs.map
6
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/parser.ts","../src/core/stream.ts"],"names":["extractJsonCandidate","text","stripMarkdown","cleaned","fenceStart","newlineAfterFence","fenceEnd","firstBrace","firstBracket","startIndex","stripComments","input","result","inString","isEscaped","len","i","char","nextNewline","endComment","normalizeSingleQuotes","inDouble","inSingle","repairJson","options","raw","_m","word","lower","stack","code","trimmedSoFar","lastChar","trailingKeyMatch","matchStr","openChar","parseStreamJson","fallback","repaired","error","StreamHealer","chunk","parsed","createStreamHealer","createHealTransformStream","healer","controller","finalResult","healAsyncIterable","stream","partial","finalObj"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * stream-heal: Zero-dependency, fault-tolerant streaming JSON parser and auto-healer\n * Production-hardened against all real-world LLM streaming edge cases.\n */\n\nexport interface ParseOptions {\n /**\n * If true, automatically strips markdown code fences (e.g. ```json ... ```)\n * and ignores conversational preambles/postambles from LLMs.\n * @default true\n */\n stripMarkdown?: boolean;\n\n /**\n * Fallback value if the JSON cannot be parsed at all yet (e.g., empty stream).\n * @default null\n */\n fallback?: unknown;\n\n /**\n * If true, throws a SyntaxError if healing fails, instead of returning the fallback.\n * @default false\n */\n strict?: boolean;\n}\n\nexport interface RepairOptions {\n /**\n * Strip markdown wrappers like ```json ... ```\n * @default true\n */\n stripMarkdown?: boolean;\n}\n\n/**\n * Extracts and sanitizes the core JSON text from an LLM response stream,\n * stripping markdown codeblock indicators and conversational fluff.\n */\nexport function extractJsonCandidate(text: string, stripMarkdown = true): string {\n if (!text || typeof text !== \"string\") return \"\";\n let cleaned = text.trim();\n\n if (!stripMarkdown) {\n return cleaned;\n }\n\n // Handle markdown code blocks e.g. ```json \\n { ... } \\n ```\n const fenceStart = cleaned.indexOf(\"```\");\n if (fenceStart !== -1) {\n // Find the end of the opening fence line (e.g. ```json\\n)\n const newlineAfterFence = cleaned.indexOf(\"\\n\", fenceStart);\n if (newlineAfterFence !== -1) {\n cleaned = cleaned.slice(newlineAfterFence + 1);\n } else {\n // Stream just started with ```json and no newline yet\n return \"\";\n }\n\n // If there is a closing fence ```, strip everything from it\n const fenceEnd = cleaned.lastIndexOf(\"```\");\n if (fenceEnd !== -1) {\n cleaned = cleaned.slice(0, fenceEnd);\n }\n }\n\n // Find the first opening bracket '{' or '['\n const firstBrace = cleaned.indexOf(\"{\");\n const firstBracket = cleaned.indexOf(\"[\");\n\n let startIndex = -1;\n if (firstBrace !== -1 && firstBracket !== -1) {\n startIndex = Math.min(firstBrace, firstBracket);\n } else if (firstBrace !== -1) {\n startIndex = firstBrace;\n } else if (firstBracket !== -1) {\n startIndex = firstBracket;\n }\n\n if (startIndex === -1) {\n return \"\";\n }\n\n return cleaned.slice(startIndex).trim();\n}\n\n/**\n * Strips JS/C-style single-line and multi-line comments outside of strings.\n */\nfunction stripComments(input: string): string {\n let result = \"\";\n let inString = false;\n let isEscaped = false;\n const len = input.length;\n\n for (let i = 0; i < len; i++) {\n const char = input[i];\n\n if (inString) {\n result += char;\n if (isEscaped) {\n isEscaped = false;\n } else if (char === \"\\\\\") {\n isEscaped = true;\n } else if (char === '\"') {\n inString = false;\n }\n continue;\n }\n\n if (char === '\"') {\n inString = true;\n result += char;\n continue;\n }\n\n // Single-line comment //\n if (char === \"/\" && i + 1 < len && input[i + 1] === \"/\") {\n const nextNewline = input.indexOf(\"\\n\", i + 2);\n if (nextNewline === -1) {\n break; // Remainder of input is a comment\n }\n i = nextNewline; // Skip to newline\n result += \"\\n\";\n continue;\n }\n\n // Multi-line comment /* ... */\n if (char === \"/\" && i + 1 < len && input[i + 1] === \"*\") {\n const endComment = input.indexOf(\"*/\", i + 2);\n if (endComment === -1) {\n break; // Unclosed block comment at stream tail\n }\n i = endComment + 1; // Skip past */\n continue;\n }\n\n result += char;\n }\n\n return result;\n}\n\n/**\n * Normalizes Python/JS single-quoted JSON (e.g. {'name': 'raj'}) to double quotes,\n * while carefully preserving apostrophes inside double-quoted strings (e.g. \"O'Connor\").\n */\nfunction normalizeSingleQuotes(text: string): string {\n let result = \"\";\n let inDouble = false;\n let inSingle = false;\n let isEscaped = false;\n\n for (let i = 0; i < text.length; i++) {\n const char = text[i];\n if (isEscaped) {\n result += char;\n isEscaped = false;\n continue;\n }\n if (char === \"\\\\\") {\n result += char;\n isEscaped = true;\n continue;\n }\n if (char === '\"' && !inSingle) {\n inDouble = !inDouble;\n result += char;\n continue;\n }\n if (char === \"'\" && !inDouble) {\n inSingle = !inSingle;\n result += '\"';\n continue;\n }\n result += char;\n }\n return result;\n}\n\n/**\n * Repairs a truncated or malformed JSON string on the fly, producing a valid JSON string\n * that can be safely parsed by native JSON.parse().\n */\nexport function repairJson(input: string, options?: RepairOptions): string {\n const stripMarkdown = options?.stripMarkdown ?? true;\n let raw = extractJsonCandidate(input, stripMarkdown);\n\n if (!raw) {\n return \"\";\n }\n\n // Strip comments outside strings\n raw = stripComments(raw);\n\n // Normalize Python-style single quotes e.g. {'name': 'raj'} -> {\"name\": \"raj\"}\n raw = normalizeSingleQuotes(raw);\n\n // Fix numbers with leading dots e.g. {\"price\": .50} -> {\"price\": 0.50}\n raw = raw.replace(/([:,\\\\[]\\s*)\\.(\\d+)/g, \"$10.$2\");\n\n // Fix numbers with leading plus e.g. {\"count\": +12} -> {\"count\": 12}\n raw = raw.replace(/([:,\\\\[]\\s*)\\+(\\d+)/g, \"$1$2\");\n\n // Fix consecutive duplicate commas e.g. [1,, 2] -> [1, 2]\n raw = raw.replace(/,\\s*,+/g, \",\");\n\n // Fix empty colon before comma e.g. {\"a\": , \"b\": 1} -> {\"a\": null, \"b\": 1}\n raw = raw.replace(/:\\s*,/g, \": null,\");\n\n // Fix unquoted string values e.g. {\"status\": active, \"role\": admin} -> {\"status\": \"active\", \"role\": \"admin\"}\n raw = raw.replace(\n /:\\s*([a-zA-Z_$][a-zA-Z0-9_$-]*)\\s*(?=([,}\\]]|$))/g,\n (_m, word) => {\n const lower = word.toLowerCase();\n if (\n lower === \"true\" ||\n lower === \"false\" ||\n lower === \"null\" ||\n word === \"None\" ||\n word === \"NaN\" ||\n word === \"Infinity\" ||\n /^t(r(u(e)?)?)?$/i.test(word) ||\n /^f(a(l(s(e)?)?)?)?$/i.test(word) ||\n /^n(u(l(l)?)?)?$/i.test(word)\n ) {\n return `: ${word}`;\n }\n return `: \"${word}\"`;\n }\n );\n\n // Fix unclosed quotes before comma and next key e.g. {\"name\":\"raj, \"age\": 25} -> {\"name\":\"raj\", \"age\": 25}\n raw = raw.replace(/(:\\s*\"[^\",\\n\\r]*),\\s*(\"([a-zA-Z0-9_$]+)\"\\s*:)/g, '$1\", $2');\n\n // Fix missing closing quotes in arrays e.g. [\"cricket, \"racing\"] -> [\"cricket\", \"racing\"]\n raw = raw.replace(/(\\[\\s*(?:(?:\"[^\"]*\",\\s*)*))\"([^\",\\n\\r]*),\\s*\"/g, '$1\"$2\", \"');\n\n // Fix stray quotes immediately preceding container closing e.g. [\"] -> [\"\"] or {\"key\": \"} -> {\"key\": \"\"}\n raw = raw.replace(/\\[\\s*\"\\s*\\]/g, '[\"\"]');\n raw = raw.replace(/:\\s*\"\\s*([}\\]])/g, ': \"\"$1');\n\n const stack: Array<\"{\" | \"[\"> = [];\n let inString = false;\n let isEscaped = false;\n let result = \"\";\n const len = raw.length;\n\n for (let i = 0; i < len; i++) {\n const char = raw[i];\n\n if (inString) {\n if (isEscaped) {\n result += char;\n isEscaped = false;\n continue;\n }\n\n if (char === \"\\\\\") {\n isEscaped = true;\n result += char;\n continue;\n }\n\n if (char === '\"') {\n inString = false;\n result += char;\n continue;\n }\n\n // Handle all unescaped control characters in strings (ASCII < 32, forbidden by JSON spec)\n const code = char.charCodeAt(0);\n if (code < 32) {\n if (char === \"\\n\") {\n result += \"\\\\n\";\n } else if (char === \"\\r\") {\n result += \"\\\\r\";\n } else if (char === \"\\t\") {\n result += \"\\\\t\";\n } else if (char === \"\\b\") {\n result += \"\\\\b\";\n } else if (char === \"\\f\") {\n result += \"\\\\f\";\n } else {\n result += \"\\\\u\" + code.toString(16).padStart(4, \"0\");\n }\n continue;\n }\n\n result += char;\n continue;\n }\n\n // --- NOT IN STRING ---\n\n if (char === '\"') {\n // Check for missing comma before this string key/value if preceded by another value\n const trimmedSoFar = result.trimEnd();\n const lastChar = trimmedSoFar[trimmedSoFar.length - 1];\n if (\n lastChar === '\"' ||\n lastChar === \"}\" ||\n lastChar === \"]\" ||\n lastChar === \"e\" || // from true\n lastChar === \"l\" // from null\n ) {\n result = trimmedSoFar + \", \";\n }\n\n inString = true;\n result += char;\n continue;\n }\n\n if (char === \"{\" || char === \"[\") {\n // Check for missing comma before nested container\n const trimmedSoFar = result.trimEnd();\n const lastChar = trimmedSoFar[trimmedSoFar.length - 1];\n if (lastChar === \"}\" || lastChar === \"]\" || lastChar === '\"') {\n result = trimmedSoFar + \", \";\n }\n\n stack.push(char);\n result += char;\n continue;\n }\n\n if (char === \"}\") {\n if (stack.length > 0 && stack[stack.length - 1] === \"{\") {\n stack.pop();\n result = result.replace(/,\\s*$/, \"\");\n result += char;\n // If the root container has closed, stop to ignore trailing conversational postambles\n if (stack.length === 0) {\n break;\n }\n }\n continue;\n }\n\n if (char === \"]\") {\n if (stack.length > 0 && stack[stack.length - 1] === \"[\") {\n stack.pop();\n result = result.replace(/,\\s*$/, \"\");\n result += char;\n // If the root container has closed, stop to ignore trailing conversational postambles\n if (stack.length === 0) {\n break;\n }\n }\n continue;\n }\n\n result += char;\n }\n\n // --- STREAM REPAIR HEURISTICS ---\n\n // 1. If currently inside a string\n if (inString) {\n if (isEscaped) {\n // Remove trailing dangling backslash e.g. \"path\\\": \\\"C:\\\\\n if (result.endsWith(\"\\\\\")) {\n result = result.slice(0, -1);\n }\n }\n // Handle incomplete Unicode escape sequences at end of string e.g. \\u, \\u0, \\u00, \\u004\n result = result.replace(/\\\\u[0-9a-fA-F]{0,3}$/, \"\");\n\n // Close the unclosed string (preserves literal content like code brackets, regexes, math)\n result += '\"';\n }\n\n // 2. Trim trailing whitespaces\n result = result.trimEnd();\n\n // 3. Normalize Python-style constants emitted by models: True, False, None, NaN, Infinity\n result = result.replace(/([:,\\\\[]\\s*)True\\b/g, \"$1true\");\n result = result.replace(/([:,\\\\[]\\s*)False\\b/g, \"$1false\");\n result = result.replace(/([:,\\\\[]\\s*)None\\b/g, \"$1null\");\n result = result.replace(/([:,\\\\[]\\s*)NaN\\b/g, \"$1null\");\n result = result.replace(/([:,\\\\[]\\s*)Infinity\\b/g, \"$1null\");\n result = result.replace(/([:,\\\\[]\\s*)-Infinity\\b/g, \"$1null\");\n\n // 4. Fix incomplete primitives at the end of the stream\n // Object values:\n result = result.replace(/:\\s*t(r(u(e)?)?)?$/i, \": true\");\n result = result.replace(/:\\s*f(a(l(s(e)?)?)?)?$/i, \": false\");\n result = result.replace(/:\\s*n(u(l(l)?)?)?$/i, \": null\");\n\n // Array items:\n result = result.replace(/([,[])\\s*t(r(u(e)?)?)?$/i, \"$1 true\");\n result = result.replace(/([,[])\\s*f(a(l(s(e)?)?)?)?$/i, \"$1 false\");\n result = result.replace(/([,[])\\s*n(u(l(l)?)?)?$/i, \"$1 null\");\n\n // Fix incomplete numbers:\n // Decimals ending with dot e.g. \"price\": 12. -> 12.0\n result = result.replace(/:\\s*(-?\\d+)\\.(?=\\s*([,}\\]]|$))/g, \": $1.0\");\n result = result.replace(/([,[])\\s*(-?\\d+)\\.(?=\\s*([,}\\]]|$))/g, \"$1 $2.0\");\n\n // Exponents in flight e.g. 1.2e, 1.2e+, 1.2e-\n result = result.replace(/:\\s*(-?\\d+(\\.\\d+)?)e[+-]?(?=\\s*([,}\\]]|$))/gi, \": $1\");\n result = result.replace(/([,[])\\s*(-?\\d+(\\.\\d+)?)e[+-]?(?=\\s*([,}\\]]|$))/gi, \"$1 $2\");\n\n // Lone minus signs e.g. \"count\": - -> 0\n result = result.replace(/:\\s*-(?=\\s*([,}\\]]|$))/g, \": 0\");\n result = result.replace(/([,[])\\s*-(?=\\s*([,}\\]]|$))/g, \"$1 0\");\n\n // 5. Clean trailing unfulfilled tokens\n // If result ends with a colon e.g. `{\"key\": `\n if (/:\\s*$/.test(result)) {\n result += \"null\";\n }\n\n // If result ends with a trailing comma e.g. `[1, 2, ` or `{\"a\": 1, `\n result = result.replace(/,\\s*$/, \"\");\n\n // Auto-quote unquoted keys emitted by open-source models e.g. {name: \"Alex\", count: 10}\n result = result.replace(/([{\\\\,]\\s*)([a-zA-Z_$][a-zA-Z0-9_$-]*)\\s*:/g, '$1\"$2\":');\n\n // If an object ended with an unfinished key without colon (e.g. `{\"key\"` or `{\"key\\\"name\"`)\n if (stack.length > 0 && stack[stack.length - 1] === \"{\") {\n const trailingKeyMatch =\n result.match(/,\\s*\"(?:[^\"\\\\]|\\\\.)*\"$/) ||\n result.match(/\\{\\s*\"(?:[^\"\\\\]|\\\\.)*\"$/);\n if (trailingKeyMatch) {\n const matchStr = trailingKeyMatch[0];\n if (matchStr.startsWith(\"{\")) {\n result = result.slice(0, result.length - matchStr.length + 1); // keep {\n } else {\n result = result.slice(0, result.length - matchStr.length);\n }\n }\n }\n\n // Re-clean trailing commas\n result = result.replace(/,\\s*$/, \"\").trimEnd();\n\n // 6. Close all unclosed brackets/braces in reverse order\n while (stack.length > 0) {\n const openChar = stack.pop();\n result = result.replace(/,\\s*$/, \"\").trimEnd();\n if (openChar === \"{\") {\n result += \"}\";\n } else if (openChar === \"[\") {\n result += \"]\";\n }\n }\n\n return result;\n}\n\n/**\n * Parses a streaming or partial JSON string into a strongly-typed JavaScript object.\n * Safe against incomplete chunks, never crashes, auto-repairs truncated streams.\n *\n * @example\n * ```ts\n * const partial = parseStreamJson<{ name: string; age: number }>('{\"name\": \"Alex\", \"age\": 2');\n * console.log(partial); // { name: \"Alex\", age: 2 }\n * ```\n */\nexport function parseStreamJson<T = unknown>(\n input: string,\n options?: ParseOptions\n): Partial<T> | null {\n const fallback = (options?.fallback ?? null) as Partial<T> | null;\n\n if (!input || typeof input !== \"string\") {\n return fallback;\n }\n\n const repaired = repairJson(input, {\n stripMarkdown: options?.stripMarkdown ?? true\n });\n\n if (!repaired) {\n return fallback;\n }\n\n try {\n return JSON.parse(repaired) as Partial<T>;\n } catch (error) {\n if (options?.strict) {\n throw error;\n }\n return fallback;\n }\n}\n","import { parseStreamJson, ParseOptions } from \"./parser\";\n\nexport interface StreamHealerOptions extends ParseOptions {\n /**\n * Callback fired whenever new valid partial JSON state is resolved.\n */\n onUpdate?: (partial: unknown) => void;\n /**\n * Callback fired if an unrecoverable syntax error occurs in strict mode.\n */\n onError?: (error: Error) => void;\n}\n\n/**\n * Stateful streaming controller for incrementally receiving chunks from LLMs.\n */\nexport class StreamHealer<T = unknown> {\n private buffer = \"\";\n private lastParsed: Partial<T> | null = null;\n private isCompleted = false;\n private options: StreamHealerOptions;\n\n constructor(options: StreamHealerOptions = {}) {\n this.options = options;\n this.lastParsed = (options.fallback ?? null) as Partial<T> | null;\n }\n\n /**\n * Appends an incremental text chunk to the stream and returns the latest repaired partial object.\n */\n public push(chunk: string): Partial<T> | null {\n if (this.isCompleted) {\n return this.lastParsed;\n }\n\n this.buffer += chunk;\n const parsed = parseStreamJson<T>(this.buffer, this.options);\n\n if (parsed !== null) {\n this.lastParsed = parsed;\n this.options.onUpdate?.(parsed);\n }\n\n return this.lastParsed;\n }\n\n /**\n * Signals that the stream has finished. Returns the final parsed object.\n */\n public finish(): Partial<T> | null {\n this.isCompleted = true;\n return this.lastParsed;\n }\n\n /**\n * Returns the raw accumulated string buffer so far.\n */\n public getRawBuffer(): string {\n return this.buffer;\n }\n\n /**\n * Returns the latest successfully repaired partial object.\n */\n public getLatest(): Partial<T> | null {\n return this.lastParsed;\n }\n\n /**\n * Resets the healer state for a new stream.\n */\n public reset(): void {\n this.buffer = \"\";\n this.lastParsed = (this.options.fallback ?? null) as Partial<T> | null;\n this.isCompleted = false;\n }\n}\n\n/**\n * Creates a stateful StreamHealer instance.\n */\nexport function createStreamHealer<T = unknown>(options?: StreamHealerOptions): StreamHealer<T> {\n return new StreamHealer<T>(options);\n}\n\n/**\n * Web Standard TransformStream compatible with Fetch API response.body.\n *\n * @example\n * ```ts\n * const response = await fetch('/api/llm-stream');\n * const jsonStream = response.body\n * .pipeThrough(new TextDecoderStream())\n * .pipeThrough(createHealTransformStream<MySchema>());\n * ```\n */\nexport function createHealTransformStream<T = unknown>(\n options?: StreamHealerOptions\n): TransformStream<string, Partial<T>> {\n const healer = new StreamHealer<T>(options);\n\n return new TransformStream<string, Partial<T>>({\n transform(chunk, controller) {\n const repaired = healer.push(chunk);\n if (repaired !== null) {\n controller.enqueue(repaired);\n }\n },\n flush(controller) {\n const finalResult = healer.finish();\n if (finalResult !== null) {\n controller.enqueue(finalResult);\n }\n }\n });\n}\n\n/**\n * Consumes an AsyncIterable of string chunks (such as from OpenAI SDK, Anthropic, or Ollama)\n * and yields the repaired partial JSON objects in real-time.\n *\n * @example\n * ```ts\n * for await (const user of healAsyncIterable<User>(openaiStream)) {\n * renderCard(user.name, user.bio);\n * }\n * ```\n */\nexport async function* healAsyncIterable<T = unknown>(\n stream: AsyncIterable<string>,\n options?: StreamHealerOptions\n): AsyncGenerator<Partial<T>, void, unknown> {\n const healer = new StreamHealer<T>(options);\n\n for await (const chunk of stream) {\n const partial = healer.push(chunk);\n if (partial !== null) {\n yield partial;\n }\n }\n\n const finalObj = healer.finish();\n if (finalObj !== null) {\n yield finalObj;\n }\n}\n\n"]}
@@ -0,0 +1,80 @@
1
+ import { StreamHealerOptions } from './index.mjs';
2
+
3
+ interface UseStreamingJsonOptions<T> extends StreamHealerOptions {
4
+ /**
5
+ * Throttle UI re-renders in milliseconds to prevent React frame drops
6
+ * during ultra-fast LLM streams (e.g., Groq, Cerebras).
7
+ * Set to 0 to disable throttling.
8
+ * @default 30
9
+ */
10
+ throttleMs?: number;
11
+ /**
12
+ * Initial data before the stream begins.
13
+ */
14
+ initialData?: Partial<T> | null;
15
+ /**
16
+ * Callback fired when stream finishes.
17
+ */
18
+ onComplete?: (finalData: Partial<T> | null) => void;
19
+ }
20
+ interface UseStreamingJsonReturn<T> {
21
+ /**
22
+ * The current repaired, valid partial JSON object.
23
+ */
24
+ data: Partial<T> | null;
25
+ /**
26
+ * True if a stream is actively in progress.
27
+ */
28
+ isLoading: boolean;
29
+ /**
30
+ * True if the stream has cleanly completed.
31
+ */
32
+ isComplete: boolean;
33
+ /**
34
+ * Any stream error encountered.
35
+ */
36
+ error: Error | null;
37
+ /**
38
+ * The full unparsed raw text buffer accumulated so far.
39
+ */
40
+ rawText: string;
41
+ /**
42
+ * Manually push an incoming chunk into the stream.
43
+ */
44
+ feed: (chunk: string) => void;
45
+ /**
46
+ * Pipe an async iterable (e.g. OpenAI stream) directly into the hook.
47
+ */
48
+ pipe: (stream: AsyncIterable<string> | ReadableStream<Uint8Array | string>) => Promise<void>;
49
+ /**
50
+ * Manually signal that the stream has finished.
51
+ */
52
+ finish: () => void;
53
+ /**
54
+ * Resets the hook to its initial idle state.
55
+ */
56
+ reset: () => void;
57
+ }
58
+ /**
59
+ * React hook for consuming streaming LLM JSON responses with zero-lag UI updates and auto-healing.
60
+ *
61
+ * @example
62
+ * ```tsx
63
+ * const { data, isLoading, pipe } = useStreamingJson<UserForm>();
64
+ *
65
+ * const handleSubmit = async () => {
66
+ * const response = await fetch('/api/ai');
67
+ * pipe(response.body!);
68
+ * };
69
+ *
70
+ * return (
71
+ * <div>
72
+ * <h2>{data?.name || "Generating..."}</h2>
73
+ * <p>{data?.bio}</p>
74
+ * </div>
75
+ * );
76
+ * ```
77
+ */
78
+ declare function useStreamingJson<T = unknown>(options?: UseStreamingJsonOptions<T>): UseStreamingJsonReturn<T>;
79
+
80
+ export { type UseStreamingJsonOptions, type UseStreamingJsonReturn, useStreamingJson };
@@ -0,0 +1,80 @@
1
+ import { StreamHealerOptions } from './index.js';
2
+
3
+ interface UseStreamingJsonOptions<T> extends StreamHealerOptions {
4
+ /**
5
+ * Throttle UI re-renders in milliseconds to prevent React frame drops
6
+ * during ultra-fast LLM streams (e.g., Groq, Cerebras).
7
+ * Set to 0 to disable throttling.
8
+ * @default 30
9
+ */
10
+ throttleMs?: number;
11
+ /**
12
+ * Initial data before the stream begins.
13
+ */
14
+ initialData?: Partial<T> | null;
15
+ /**
16
+ * Callback fired when stream finishes.
17
+ */
18
+ onComplete?: (finalData: Partial<T> | null) => void;
19
+ }
20
+ interface UseStreamingJsonReturn<T> {
21
+ /**
22
+ * The current repaired, valid partial JSON object.
23
+ */
24
+ data: Partial<T> | null;
25
+ /**
26
+ * True if a stream is actively in progress.
27
+ */
28
+ isLoading: boolean;
29
+ /**
30
+ * True if the stream has cleanly completed.
31
+ */
32
+ isComplete: boolean;
33
+ /**
34
+ * Any stream error encountered.
35
+ */
36
+ error: Error | null;
37
+ /**
38
+ * The full unparsed raw text buffer accumulated so far.
39
+ */
40
+ rawText: string;
41
+ /**
42
+ * Manually push an incoming chunk into the stream.
43
+ */
44
+ feed: (chunk: string) => void;
45
+ /**
46
+ * Pipe an async iterable (e.g. OpenAI stream) directly into the hook.
47
+ */
48
+ pipe: (stream: AsyncIterable<string> | ReadableStream<Uint8Array | string>) => Promise<void>;
49
+ /**
50
+ * Manually signal that the stream has finished.
51
+ */
52
+ finish: () => void;
53
+ /**
54
+ * Resets the hook to its initial idle state.
55
+ */
56
+ reset: () => void;
57
+ }
58
+ /**
59
+ * React hook for consuming streaming LLM JSON responses with zero-lag UI updates and auto-healing.
60
+ *
61
+ * @example
62
+ * ```tsx
63
+ * const { data, isLoading, pipe } = useStreamingJson<UserForm>();
64
+ *
65
+ * const handleSubmit = async () => {
66
+ * const response = await fetch('/api/ai');
67
+ * pipe(response.body!);
68
+ * };
69
+ *
70
+ * return (
71
+ * <div>
72
+ * <h2>{data?.name || "Generating..."}</h2>
73
+ * <p>{data?.bio}</p>
74
+ * </div>
75
+ * );
76
+ * ```
77
+ */
78
+ declare function useStreamingJson<T = unknown>(options?: UseStreamingJsonOptions<T>): UseStreamingJsonReturn<T>;
79
+
80
+ export { type UseStreamingJsonOptions, type UseStreamingJsonReturn, useStreamingJson };
package/dist/react.js ADDED
@@ -0,0 +1,6 @@
1
+ 'use strict';var react=require('react');function N(o,i=true){if(!o||typeof o!="string")return "";let n=o.trim();if(!i)return n;let t=n.indexOf("```");if(t!==-1){let e=n.indexOf(`
2
+ `,t);if(e!==-1)n=n.slice(e+1);else return "";let h=n.lastIndexOf("```");h!==-1&&(n=n.slice(0,h));}let s=n.indexOf("{"),a=n.indexOf("["),l=-1;return s!==-1&&a!==-1?l=Math.min(s,a):s!==-1?l=s:a!==-1&&(l=a),l===-1?"":n.slice(l).trim()}function z(o){let i="",n=false,t=false,s=o.length;for(let a=0;a<s;a++){let l=o[a];if(n){i+=l,t?t=false:l==="\\"?t=true:l==='"'&&(n=false);continue}if(l==='"'){n=true,i+=l;continue}if(l==="/"&&a+1<s&&o[a+1]==="/"){let e=o.indexOf(`
3
+ `,a+2);if(e===-1)break;a=e,i+=`
4
+ `;continue}if(l==="/"&&a+1<s&&o[a+1]==="*"){let e=o.indexOf("*/",a+2);if(e===-1)break;a=e+1;continue}i+=l;}return i}function L(o){let i="",n=false,t=false,s=false;for(let a=0;a<o.length;a++){let l=o[a];if(s){i+=l,s=false;continue}if(l==="\\"){i+=l,s=true;continue}if(l==='"'&&!t){n=!n,i+=l;continue}if(l==="'"&&!n){t=!t,i+='"';continue}i+=l;}return i}function _(o,i){let n=i?.stripMarkdown??true,t=N(o,n);if(!t)return "";t=z(t),t=L(t),t=t.replace(/([:,\\[]\s*)\.(\d+)/g,"$10.$2"),t=t.replace(/([:,\\[]\s*)\+(\d+)/g,"$1$2"),t=t.replace(/,\s*,+/g,","),t=t.replace(/:\s*,/g,": null,"),t=t.replace(/:\s*([a-zA-Z_$][a-zA-Z0-9_$-]*)\s*(?=([,}\]]|$))/g,(f,r)=>{let u=r.toLowerCase();return u==="true"||u==="false"||u==="null"||r==="None"||r==="NaN"||r==="Infinity"||/^t(r(u(e)?)?)?$/i.test(r)||/^f(a(l(s(e)?)?)?)?$/i.test(r)||/^n(u(l(l)?)?)?$/i.test(r)?`: ${r}`:`: "${r}"`}),t=t.replace(/(:\s*"[^",\n\r]*),\s*("([a-zA-Z0-9_$]+)"\s*:)/g,'$1", $2'),t=t.replace(/(\[\s*(?:(?:"[^"]*",\s*)*))"([^",\n\r]*),\s*"/g,'$1"$2", "'),t=t.replace(/\[\s*"\s*\]/g,'[""]'),t=t.replace(/:\s*"\s*([}\]])/g,': ""$1');let s=[],a=false,l=false,e="",h=t.length;for(let f=0;f<h;f++){let r=t[f];if(a){if(l){e+=r,l=false;continue}if(r==="\\"){l=true,e+=r;continue}if(r==='"'){a=false,e+=r;continue}let u=r.charCodeAt(0);if(u<32){r===`
5
+ `?e+="\\n":r==="\r"?e+="\\r":r===" "?e+="\\t":r==="\b"?e+="\\b":r==="\f"?e+="\\f":e+="\\u"+u.toString(16).padStart(4,"0");continue}e+=r;continue}if(r==='"'){let u=e.trimEnd(),p=u[u.length-1];(p==='"'||p==="}"||p==="]"||p==="e"||p==="l")&&(e=u+", "),a=true,e+=r;continue}if(r==="{"||r==="["){let u=e.trimEnd(),p=u[u.length-1];(p==="}"||p==="]"||p==='"')&&(e=u+", "),s.push(r),e+=r;continue}if(r==="}"){if(s.length>0&&s[s.length-1]==="{"&&(s.pop(),e=e.replace(/,\s*$/,""),e+=r,s.length===0))break;continue}if(r==="]"){if(s.length>0&&s[s.length-1]==="["&&(s.pop(),e=e.replace(/,\s*$/,""),e+=r,s.length===0))break;continue}e+=r;}if(a&&(l&&e.endsWith("\\")&&(e=e.slice(0,-1)),e=e.replace(/\\u[0-9a-fA-F]{0,3}$/,""),e+='"'),e=e.trimEnd(),e=e.replace(/([:,\\[]\s*)True\b/g,"$1true"),e=e.replace(/([:,\\[]\s*)False\b/g,"$1false"),e=e.replace(/([:,\\[]\s*)None\b/g,"$1null"),e=e.replace(/([:,\\[]\s*)NaN\b/g,"$1null"),e=e.replace(/([:,\\[]\s*)Infinity\b/g,"$1null"),e=e.replace(/([:,\\[]\s*)-Infinity\b/g,"$1null"),e=e.replace(/:\s*t(r(u(e)?)?)?$/i,": true"),e=e.replace(/:\s*f(a(l(s(e)?)?)?)?$/i,": false"),e=e.replace(/:\s*n(u(l(l)?)?)?$/i,": null"),e=e.replace(/([,[])\s*t(r(u(e)?)?)?$/i,"$1 true"),e=e.replace(/([,[])\s*f(a(l(s(e)?)?)?)?$/i,"$1 false"),e=e.replace(/([,[])\s*n(u(l(l)?)?)?$/i,"$1 null"),e=e.replace(/:\s*(-?\d+)\.(?=\s*([,}\]]|$))/g,": $1.0"),e=e.replace(/([,[])\s*(-?\d+)\.(?=\s*([,}\]]|$))/g,"$1 $2.0"),e=e.replace(/:\s*(-?\d+(\.\d+)?)e[+-]?(?=\s*([,}\]]|$))/gi,": $1"),e=e.replace(/([,[])\s*(-?\d+(\.\d+)?)e[+-]?(?=\s*([,}\]]|$))/gi,"$1 $2"),e=e.replace(/:\s*-(?=\s*([,}\]]|$))/g,": 0"),e=e.replace(/([,[])\s*-(?=\s*([,}\]]|$))/g,"$1 0"),/:\s*$/.test(e)&&(e+="null"),e=e.replace(/,\s*$/,""),e=e.replace(/([{\\,]\s*)([a-zA-Z_$][a-zA-Z0-9_$-]*)\s*:/g,'$1"$2":'),s.length>0&&s[s.length-1]==="{"){let f=e.match(/,\s*"(?:[^"\\]|\\.)*"$/)||e.match(/\{\s*"(?:[^"\\]|\\.)*"$/);if(f){let r=f[0];r.startsWith("{")?e=e.slice(0,e.length-r.length+1):e=e.slice(0,e.length-r.length);}}for(e=e.replace(/,\s*$/,"").trimEnd();s.length>0;){let f=s.pop();e=e.replace(/,\s*$/,"").trimEnd(),f==="{"?e+="}":f==="["&&(e+="]");}return e}function H(o,i){let n=i?.fallback??null;if(!o||typeof o!="string")return n;let t=_(o,{stripMarkdown:i?.stripMarkdown??true});if(!t)return n;try{return JSON.parse(t)}catch(s){if(i?.strict)throw s;return n}}var k=class{buffer="";lastParsed=null;isCompleted=false;options;constructor(i={}){this.options=i,this.lastParsed=i.fallback??null;}push(i){if(this.isCompleted)return this.lastParsed;this.buffer+=i;let n=H(this.buffer,this.options);return n!==null&&(this.lastParsed=n,this.options.onUpdate?.(n)),this.lastParsed}finish(){return this.isCompleted=true,this.lastParsed}getRawBuffer(){return this.buffer}getLatest(){return this.lastParsed}reset(){this.buffer="",this.lastParsed=this.options.fallback??null,this.isCompleted=false;}};function K(o={}){let{throttleMs:i=30,initialData:n=null,onComplete:t,onUpdate:s,onError:a,...l}=o,[e,h]=react.useState(n),[f,r]=react.useState(false),[u,p]=react.useState(false),[I,O]=react.useState(null),[U,x]=react.useState(""),m=react.useRef(null),y=react.useRef(0),d=react.useRef(null),A=react.useRef(n);m.current||(m.current=new k({...l,onUpdate:c=>{s?.(c);},onError:c=>{O(c),a?.(c);}})),react.useEffect(()=>()=>{d.current&&clearTimeout(d.current);},[]);let v=react.useCallback((c,g)=>{if(A.current=c,x(g),i<=0){h(c);return}let $=performance.now(),w=$-y.current;w>=i?(y.current=$,h(c)):d.current||(d.current=setTimeout(()=>{y.current=performance.now(),h(A.current),d.current=null;},i-w));},[i]),S=react.useCallback(c=>{if(!m.current)return;r(true);let g=m.current.push(c);v(g,m.current.getRawBuffer());},[v]),R=react.useCallback(()=>{if(!m.current)return;d.current&&(clearTimeout(d.current),d.current=null);let c=m.current.finish();h(c),x(m.current.getRawBuffer()),r(false),p(true),t?.(c);},[t]),C=react.useCallback(()=>{d.current&&(clearTimeout(d.current),d.current=null),m.current?.reset(),h(n),x(""),r(false),p(false),O(null);},[n]),J=react.useCallback(async c=>{C(),r(true);try{if("getReader"in c){let g=c.getReader(),$=new TextDecoder;for(;;){let{done:w,value:E}=await g.read();if(w)break;let M=typeof E=="string"?E:$.decode(E,{stream:!0});S(M);}}else for await(let g of c)S(g);}catch(g){let $=g instanceof Error?g:new Error(String(g));O($),a?.($);}finally{R();}},[S,R,C,a]);return {data:e,isLoading:f,isComplete:u,error:I,rawText:U,feed:S,pipe:J,finish:R,reset:C}}exports.useStreamingJson=K;//# sourceMappingURL=react.js.map
6
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/parser.ts","../src/core/stream.ts","../src/react/index.ts"],"names":["extractJsonCandidate","text","stripMarkdown","cleaned","fenceStart","newlineAfterFence","fenceEnd","firstBrace","firstBracket","startIndex","stripComments","input","result","inString","isEscaped","len","i","char","nextNewline","endComment","normalizeSingleQuotes","inDouble","inSingle","repairJson","options","raw","_m","word","lower","stack","code","trimmedSoFar","lastChar","trailingKeyMatch","matchStr","openChar","parseStreamJson","fallback","repaired","error","StreamHealer","chunk","parsed","useStreamingJson","throttleMs","initialData","onComplete","onUpdate","onError","parserOptions","data","setData","useState","isLoading","setIsLoading","isComplete","setIsComplete","setError","rawText","setRawText","healerRef","useRef","lastRenderTimeRef","pendingUpdateRef","latestDataRef","partial","err","useEffect","scheduleRender","useCallback","nextData","now","timeSinceLast","feed","finish","finalResult","reset","pipe","stream","reader","decoder","done","value","textChunk","errorObj"],"mappings":"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","file":"react.js","sourcesContent":["/**\n * stream-heal: Zero-dependency, fault-tolerant streaming JSON parser and auto-healer\n * Production-hardened against all real-world LLM streaming edge cases.\n */\n\nexport interface ParseOptions {\n /**\n * If true, automatically strips markdown code fences (e.g. ```json ... ```)\n * and ignores conversational preambles/postambles from LLMs.\n * @default true\n */\n stripMarkdown?: boolean;\n\n /**\n * Fallback value if the JSON cannot be parsed at all yet (e.g., empty stream).\n * @default null\n */\n fallback?: unknown;\n\n /**\n * If true, throws a SyntaxError if healing fails, instead of returning the fallback.\n * @default false\n */\n strict?: boolean;\n}\n\nexport interface RepairOptions {\n /**\n * Strip markdown wrappers like ```json ... ```\n * @default true\n */\n stripMarkdown?: boolean;\n}\n\n/**\n * Extracts and sanitizes the core JSON text from an LLM response stream,\n * stripping markdown codeblock indicators and conversational fluff.\n */\nexport function extractJsonCandidate(text: string, stripMarkdown = true): string {\n if (!text || typeof text !== \"string\") return \"\";\n let cleaned = text.trim();\n\n if (!stripMarkdown) {\n return cleaned;\n }\n\n // Handle markdown code blocks e.g. ```json \\n { ... } \\n ```\n const fenceStart = cleaned.indexOf(\"```\");\n if (fenceStart !== -1) {\n // Find the end of the opening fence line (e.g. ```json\\n)\n const newlineAfterFence = cleaned.indexOf(\"\\n\", fenceStart);\n if (newlineAfterFence !== -1) {\n cleaned = cleaned.slice(newlineAfterFence + 1);\n } else {\n // Stream just started with ```json and no newline yet\n return \"\";\n }\n\n // If there is a closing fence ```, strip everything from it\n const fenceEnd = cleaned.lastIndexOf(\"```\");\n if (fenceEnd !== -1) {\n cleaned = cleaned.slice(0, fenceEnd);\n }\n }\n\n // Find the first opening bracket '{' or '['\n const firstBrace = cleaned.indexOf(\"{\");\n const firstBracket = cleaned.indexOf(\"[\");\n\n let startIndex = -1;\n if (firstBrace !== -1 && firstBracket !== -1) {\n startIndex = Math.min(firstBrace, firstBracket);\n } else if (firstBrace !== -1) {\n startIndex = firstBrace;\n } else if (firstBracket !== -1) {\n startIndex = firstBracket;\n }\n\n if (startIndex === -1) {\n return \"\";\n }\n\n return cleaned.slice(startIndex).trim();\n}\n\n/**\n * Strips JS/C-style single-line and multi-line comments outside of strings.\n */\nfunction stripComments(input: string): string {\n let result = \"\";\n let inString = false;\n let isEscaped = false;\n const len = input.length;\n\n for (let i = 0; i < len; i++) {\n const char = input[i];\n\n if (inString) {\n result += char;\n if (isEscaped) {\n isEscaped = false;\n } else if (char === \"\\\\\") {\n isEscaped = true;\n } else if (char === '\"') {\n inString = false;\n }\n continue;\n }\n\n if (char === '\"') {\n inString = true;\n result += char;\n continue;\n }\n\n // Single-line comment //\n if (char === \"/\" && i + 1 < len && input[i + 1] === \"/\") {\n const nextNewline = input.indexOf(\"\\n\", i + 2);\n if (nextNewline === -1) {\n break; // Remainder of input is a comment\n }\n i = nextNewline; // Skip to newline\n result += \"\\n\";\n continue;\n }\n\n // Multi-line comment /* ... */\n if (char === \"/\" && i + 1 < len && input[i + 1] === \"*\") {\n const endComment = input.indexOf(\"*/\", i + 2);\n if (endComment === -1) {\n break; // Unclosed block comment at stream tail\n }\n i = endComment + 1; // Skip past */\n continue;\n }\n\n result += char;\n }\n\n return result;\n}\n\n/**\n * Normalizes Python/JS single-quoted JSON (e.g. {'name': 'raj'}) to double quotes,\n * while carefully preserving apostrophes inside double-quoted strings (e.g. \"O'Connor\").\n */\nfunction normalizeSingleQuotes(text: string): string {\n let result = \"\";\n let inDouble = false;\n let inSingle = false;\n let isEscaped = false;\n\n for (let i = 0; i < text.length; i++) {\n const char = text[i];\n if (isEscaped) {\n result += char;\n isEscaped = false;\n continue;\n }\n if (char === \"\\\\\") {\n result += char;\n isEscaped = true;\n continue;\n }\n if (char === '\"' && !inSingle) {\n inDouble = !inDouble;\n result += char;\n continue;\n }\n if (char === \"'\" && !inDouble) {\n inSingle = !inSingle;\n result += '\"';\n continue;\n }\n result += char;\n }\n return result;\n}\n\n/**\n * Repairs a truncated or malformed JSON string on the fly, producing a valid JSON string\n * that can be safely parsed by native JSON.parse().\n */\nexport function repairJson(input: string, options?: RepairOptions): string {\n const stripMarkdown = options?.stripMarkdown ?? true;\n let raw = extractJsonCandidate(input, stripMarkdown);\n\n if (!raw) {\n return \"\";\n }\n\n // Strip comments outside strings\n raw = stripComments(raw);\n\n // Normalize Python-style single quotes e.g. {'name': 'raj'} -> {\"name\": \"raj\"}\n raw = normalizeSingleQuotes(raw);\n\n // Fix numbers with leading dots e.g. {\"price\": .50} -> {\"price\": 0.50}\n raw = raw.replace(/([:,\\\\[]\\s*)\\.(\\d+)/g, \"$10.$2\");\n\n // Fix numbers with leading plus e.g. {\"count\": +12} -> {\"count\": 12}\n raw = raw.replace(/([:,\\\\[]\\s*)\\+(\\d+)/g, \"$1$2\");\n\n // Fix consecutive duplicate commas e.g. [1,, 2] -> [1, 2]\n raw = raw.replace(/,\\s*,+/g, \",\");\n\n // Fix empty colon before comma e.g. {\"a\": , \"b\": 1} -> {\"a\": null, \"b\": 1}\n raw = raw.replace(/:\\s*,/g, \": null,\");\n\n // Fix unquoted string values e.g. {\"status\": active, \"role\": admin} -> {\"status\": \"active\", \"role\": \"admin\"}\n raw = raw.replace(\n /:\\s*([a-zA-Z_$][a-zA-Z0-9_$-]*)\\s*(?=([,}\\]]|$))/g,\n (_m, word) => {\n const lower = word.toLowerCase();\n if (\n lower === \"true\" ||\n lower === \"false\" ||\n lower === \"null\" ||\n word === \"None\" ||\n word === \"NaN\" ||\n word === \"Infinity\" ||\n /^t(r(u(e)?)?)?$/i.test(word) ||\n /^f(a(l(s(e)?)?)?)?$/i.test(word) ||\n /^n(u(l(l)?)?)?$/i.test(word)\n ) {\n return `: ${word}`;\n }\n return `: \"${word}\"`;\n }\n );\n\n // Fix unclosed quotes before comma and next key e.g. {\"name\":\"raj, \"age\": 25} -> {\"name\":\"raj\", \"age\": 25}\n raw = raw.replace(/(:\\s*\"[^\",\\n\\r]*),\\s*(\"([a-zA-Z0-9_$]+)\"\\s*:)/g, '$1\", $2');\n\n // Fix missing closing quotes in arrays e.g. [\"cricket, \"racing\"] -> [\"cricket\", \"racing\"]\n raw = raw.replace(/(\\[\\s*(?:(?:\"[^\"]*\",\\s*)*))\"([^\",\\n\\r]*),\\s*\"/g, '$1\"$2\", \"');\n\n // Fix stray quotes immediately preceding container closing e.g. [\"] -> [\"\"] or {\"key\": \"} -> {\"key\": \"\"}\n raw = raw.replace(/\\[\\s*\"\\s*\\]/g, '[\"\"]');\n raw = raw.replace(/:\\s*\"\\s*([}\\]])/g, ': \"\"$1');\n\n const stack: Array<\"{\" | \"[\"> = [];\n let inString = false;\n let isEscaped = false;\n let result = \"\";\n const len = raw.length;\n\n for (let i = 0; i < len; i++) {\n const char = raw[i];\n\n if (inString) {\n if (isEscaped) {\n result += char;\n isEscaped = false;\n continue;\n }\n\n if (char === \"\\\\\") {\n isEscaped = true;\n result += char;\n continue;\n }\n\n if (char === '\"') {\n inString = false;\n result += char;\n continue;\n }\n\n // Handle all unescaped control characters in strings (ASCII < 32, forbidden by JSON spec)\n const code = char.charCodeAt(0);\n if (code < 32) {\n if (char === \"\\n\") {\n result += \"\\\\n\";\n } else if (char === \"\\r\") {\n result += \"\\\\r\";\n } else if (char === \"\\t\") {\n result += \"\\\\t\";\n } else if (char === \"\\b\") {\n result += \"\\\\b\";\n } else if (char === \"\\f\") {\n result += \"\\\\f\";\n } else {\n result += \"\\\\u\" + code.toString(16).padStart(4, \"0\");\n }\n continue;\n }\n\n result += char;\n continue;\n }\n\n // --- NOT IN STRING ---\n\n if (char === '\"') {\n // Check for missing comma before this string key/value if preceded by another value\n const trimmedSoFar = result.trimEnd();\n const lastChar = trimmedSoFar[trimmedSoFar.length - 1];\n if (\n lastChar === '\"' ||\n lastChar === \"}\" ||\n lastChar === \"]\" ||\n lastChar === \"e\" || // from true\n lastChar === \"l\" // from null\n ) {\n result = trimmedSoFar + \", \";\n }\n\n inString = true;\n result += char;\n continue;\n }\n\n if (char === \"{\" || char === \"[\") {\n // Check for missing comma before nested container\n const trimmedSoFar = result.trimEnd();\n const lastChar = trimmedSoFar[trimmedSoFar.length - 1];\n if (lastChar === \"}\" || lastChar === \"]\" || lastChar === '\"') {\n result = trimmedSoFar + \", \";\n }\n\n stack.push(char);\n result += char;\n continue;\n }\n\n if (char === \"}\") {\n if (stack.length > 0 && stack[stack.length - 1] === \"{\") {\n stack.pop();\n result = result.replace(/,\\s*$/, \"\");\n result += char;\n // If the root container has closed, stop to ignore trailing conversational postambles\n if (stack.length === 0) {\n break;\n }\n }\n continue;\n }\n\n if (char === \"]\") {\n if (stack.length > 0 && stack[stack.length - 1] === \"[\") {\n stack.pop();\n result = result.replace(/,\\s*$/, \"\");\n result += char;\n // If the root container has closed, stop to ignore trailing conversational postambles\n if (stack.length === 0) {\n break;\n }\n }\n continue;\n }\n\n result += char;\n }\n\n // --- STREAM REPAIR HEURISTICS ---\n\n // 1. If currently inside a string\n if (inString) {\n if (isEscaped) {\n // Remove trailing dangling backslash e.g. \"path\\\": \\\"C:\\\\\n if (result.endsWith(\"\\\\\")) {\n result = result.slice(0, -1);\n }\n }\n // Handle incomplete Unicode escape sequences at end of string e.g. \\u, \\u0, \\u00, \\u004\n result = result.replace(/\\\\u[0-9a-fA-F]{0,3}$/, \"\");\n\n // Close the unclosed string (preserves literal content like code brackets, regexes, math)\n result += '\"';\n }\n\n // 2. Trim trailing whitespaces\n result = result.trimEnd();\n\n // 3. Normalize Python-style constants emitted by models: True, False, None, NaN, Infinity\n result = result.replace(/([:,\\\\[]\\s*)True\\b/g, \"$1true\");\n result = result.replace(/([:,\\\\[]\\s*)False\\b/g, \"$1false\");\n result = result.replace(/([:,\\\\[]\\s*)None\\b/g, \"$1null\");\n result = result.replace(/([:,\\\\[]\\s*)NaN\\b/g, \"$1null\");\n result = result.replace(/([:,\\\\[]\\s*)Infinity\\b/g, \"$1null\");\n result = result.replace(/([:,\\\\[]\\s*)-Infinity\\b/g, \"$1null\");\n\n // 4. Fix incomplete primitives at the end of the stream\n // Object values:\n result = result.replace(/:\\s*t(r(u(e)?)?)?$/i, \": true\");\n result = result.replace(/:\\s*f(a(l(s(e)?)?)?)?$/i, \": false\");\n result = result.replace(/:\\s*n(u(l(l)?)?)?$/i, \": null\");\n\n // Array items:\n result = result.replace(/([,[])\\s*t(r(u(e)?)?)?$/i, \"$1 true\");\n result = result.replace(/([,[])\\s*f(a(l(s(e)?)?)?)?$/i, \"$1 false\");\n result = result.replace(/([,[])\\s*n(u(l(l)?)?)?$/i, \"$1 null\");\n\n // Fix incomplete numbers:\n // Decimals ending with dot e.g. \"price\": 12. -> 12.0\n result = result.replace(/:\\s*(-?\\d+)\\.(?=\\s*([,}\\]]|$))/g, \": $1.0\");\n result = result.replace(/([,[])\\s*(-?\\d+)\\.(?=\\s*([,}\\]]|$))/g, \"$1 $2.0\");\n\n // Exponents in flight e.g. 1.2e, 1.2e+, 1.2e-\n result = result.replace(/:\\s*(-?\\d+(\\.\\d+)?)e[+-]?(?=\\s*([,}\\]]|$))/gi, \": $1\");\n result = result.replace(/([,[])\\s*(-?\\d+(\\.\\d+)?)e[+-]?(?=\\s*([,}\\]]|$))/gi, \"$1 $2\");\n\n // Lone minus signs e.g. \"count\": - -> 0\n result = result.replace(/:\\s*-(?=\\s*([,}\\]]|$))/g, \": 0\");\n result = result.replace(/([,[])\\s*-(?=\\s*([,}\\]]|$))/g, \"$1 0\");\n\n // 5. Clean trailing unfulfilled tokens\n // If result ends with a colon e.g. `{\"key\": `\n if (/:\\s*$/.test(result)) {\n result += \"null\";\n }\n\n // If result ends with a trailing comma e.g. `[1, 2, ` or `{\"a\": 1, `\n result = result.replace(/,\\s*$/, \"\");\n\n // Auto-quote unquoted keys emitted by open-source models e.g. {name: \"Alex\", count: 10}\n result = result.replace(/([{\\\\,]\\s*)([a-zA-Z_$][a-zA-Z0-9_$-]*)\\s*:/g, '$1\"$2\":');\n\n // If an object ended with an unfinished key without colon (e.g. `{\"key\"` or `{\"key\\\"name\"`)\n if (stack.length > 0 && stack[stack.length - 1] === \"{\") {\n const trailingKeyMatch =\n result.match(/,\\s*\"(?:[^\"\\\\]|\\\\.)*\"$/) ||\n result.match(/\\{\\s*\"(?:[^\"\\\\]|\\\\.)*\"$/);\n if (trailingKeyMatch) {\n const matchStr = trailingKeyMatch[0];\n if (matchStr.startsWith(\"{\")) {\n result = result.slice(0, result.length - matchStr.length + 1); // keep {\n } else {\n result = result.slice(0, result.length - matchStr.length);\n }\n }\n }\n\n // Re-clean trailing commas\n result = result.replace(/,\\s*$/, \"\").trimEnd();\n\n // 6. Close all unclosed brackets/braces in reverse order\n while (stack.length > 0) {\n const openChar = stack.pop();\n result = result.replace(/,\\s*$/, \"\").trimEnd();\n if (openChar === \"{\") {\n result += \"}\";\n } else if (openChar === \"[\") {\n result += \"]\";\n }\n }\n\n return result;\n}\n\n/**\n * Parses a streaming or partial JSON string into a strongly-typed JavaScript object.\n * Safe against incomplete chunks, never crashes, auto-repairs truncated streams.\n *\n * @example\n * ```ts\n * const partial = parseStreamJson<{ name: string; age: number }>('{\"name\": \"Alex\", \"age\": 2');\n * console.log(partial); // { name: \"Alex\", age: 2 }\n * ```\n */\nexport function parseStreamJson<T = unknown>(\n input: string,\n options?: ParseOptions\n): Partial<T> | null {\n const fallback = (options?.fallback ?? null) as Partial<T> | null;\n\n if (!input || typeof input !== \"string\") {\n return fallback;\n }\n\n const repaired = repairJson(input, {\n stripMarkdown: options?.stripMarkdown ?? true\n });\n\n if (!repaired) {\n return fallback;\n }\n\n try {\n return JSON.parse(repaired) as Partial<T>;\n } catch (error) {\n if (options?.strict) {\n throw error;\n }\n return fallback;\n }\n}\n","import { parseStreamJson, ParseOptions } from \"./parser\";\n\nexport interface StreamHealerOptions extends ParseOptions {\n /**\n * Callback fired whenever new valid partial JSON state is resolved.\n */\n onUpdate?: (partial: unknown) => void;\n /**\n * Callback fired if an unrecoverable syntax error occurs in strict mode.\n */\n onError?: (error: Error) => void;\n}\n\n/**\n * Stateful streaming controller for incrementally receiving chunks from LLMs.\n */\nexport class StreamHealer<T = unknown> {\n private buffer = \"\";\n private lastParsed: Partial<T> | null = null;\n private isCompleted = false;\n private options: StreamHealerOptions;\n\n constructor(options: StreamHealerOptions = {}) {\n this.options = options;\n this.lastParsed = (options.fallback ?? null) as Partial<T> | null;\n }\n\n /**\n * Appends an incremental text chunk to the stream and returns the latest repaired partial object.\n */\n public push(chunk: string): Partial<T> | null {\n if (this.isCompleted) {\n return this.lastParsed;\n }\n\n this.buffer += chunk;\n const parsed = parseStreamJson<T>(this.buffer, this.options);\n\n if (parsed !== null) {\n this.lastParsed = parsed;\n this.options.onUpdate?.(parsed);\n }\n\n return this.lastParsed;\n }\n\n /**\n * Signals that the stream has finished. Returns the final parsed object.\n */\n public finish(): Partial<T> | null {\n this.isCompleted = true;\n return this.lastParsed;\n }\n\n /**\n * Returns the raw accumulated string buffer so far.\n */\n public getRawBuffer(): string {\n return this.buffer;\n }\n\n /**\n * Returns the latest successfully repaired partial object.\n */\n public getLatest(): Partial<T> | null {\n return this.lastParsed;\n }\n\n /**\n * Resets the healer state for a new stream.\n */\n public reset(): void {\n this.buffer = \"\";\n this.lastParsed = (this.options.fallback ?? null) as Partial<T> | null;\n this.isCompleted = false;\n }\n}\n\n/**\n * Creates a stateful StreamHealer instance.\n */\nexport function createStreamHealer<T = unknown>(options?: StreamHealerOptions): StreamHealer<T> {\n return new StreamHealer<T>(options);\n}\n\n/**\n * Web Standard TransformStream compatible with Fetch API response.body.\n *\n * @example\n * ```ts\n * const response = await fetch('/api/llm-stream');\n * const jsonStream = response.body\n * .pipeThrough(new TextDecoderStream())\n * .pipeThrough(createHealTransformStream<MySchema>());\n * ```\n */\nexport function createHealTransformStream<T = unknown>(\n options?: StreamHealerOptions\n): TransformStream<string, Partial<T>> {\n const healer = new StreamHealer<T>(options);\n\n return new TransformStream<string, Partial<T>>({\n transform(chunk, controller) {\n const repaired = healer.push(chunk);\n if (repaired !== null) {\n controller.enqueue(repaired);\n }\n },\n flush(controller) {\n const finalResult = healer.finish();\n if (finalResult !== null) {\n controller.enqueue(finalResult);\n }\n }\n });\n}\n\n/**\n * Consumes an AsyncIterable of string chunks (such as from OpenAI SDK, Anthropic, or Ollama)\n * and yields the repaired partial JSON objects in real-time.\n *\n * @example\n * ```ts\n * for await (const user of healAsyncIterable<User>(openaiStream)) {\n * renderCard(user.name, user.bio);\n * }\n * ```\n */\nexport async function* healAsyncIterable<T = unknown>(\n stream: AsyncIterable<string>,\n options?: StreamHealerOptions\n): AsyncGenerator<Partial<T>, void, unknown> {\n const healer = new StreamHealer<T>(options);\n\n for await (const chunk of stream) {\n const partial = healer.push(chunk);\n if (partial !== null) {\n yield partial;\n }\n }\n\n const finalObj = healer.finish();\n if (finalObj !== null) {\n yield finalObj;\n }\n}\n\n","import { useState, useRef, useCallback, useEffect } from \"react\";\nimport { StreamHealer, StreamHealerOptions } from \"../core/stream\";\n\nexport interface UseStreamingJsonOptions<T> extends StreamHealerOptions {\n /**\n * Throttle UI re-renders in milliseconds to prevent React frame drops\n * during ultra-fast LLM streams (e.g., Groq, Cerebras).\n * Set to 0 to disable throttling.\n * @default 30\n */\n throttleMs?: number;\n\n /**\n * Initial data before the stream begins.\n */\n initialData?: Partial<T> | null;\n\n /**\n * Callback fired when stream finishes.\n */\n onComplete?: (finalData: Partial<T> | null) => void;\n}\n\nexport interface UseStreamingJsonReturn<T> {\n /**\n * The current repaired, valid partial JSON object.\n */\n data: Partial<T> | null;\n\n /**\n * True if a stream is actively in progress.\n */\n isLoading: boolean;\n\n /**\n * True if the stream has cleanly completed.\n */\n isComplete: boolean;\n\n /**\n * Any stream error encountered.\n */\n error: Error | null;\n\n /**\n * The full unparsed raw text buffer accumulated so far.\n */\n rawText: string;\n\n /**\n * Manually push an incoming chunk into the stream.\n */\n feed: (chunk: string) => void;\n\n /**\n * Pipe an async iterable (e.g. OpenAI stream) directly into the hook.\n */\n pipe: (stream: AsyncIterable<string> | ReadableStream<Uint8Array | string>) => Promise<void>;\n\n /**\n * Manually signal that the stream has finished.\n */\n finish: () => void;\n\n /**\n * Resets the hook to its initial idle state.\n */\n reset: () => void;\n}\n\n/**\n * React hook for consuming streaming LLM JSON responses with zero-lag UI updates and auto-healing.\n *\n * @example\n * ```tsx\n * const { data, isLoading, pipe } = useStreamingJson<UserForm>();\n *\n * const handleSubmit = async () => {\n * const response = await fetch('/api/ai');\n * pipe(response.body!);\n * };\n *\n * return (\n * <div>\n * <h2>{data?.name || \"Generating...\"}</h2>\n * <p>{data?.bio}</p>\n * </div>\n * );\n * ```\n */\nexport function useStreamingJson<T = unknown>(\n options: UseStreamingJsonOptions<T> = {}\n): UseStreamingJsonReturn<T> {\n const {\n throttleMs = 30,\n initialData = null,\n onComplete,\n onUpdate,\n onError,\n ...parserOptions\n } = options;\n\n const [data, setData] = useState<Partial<T> | null>(initialData);\n const [isLoading, setIsLoading] = useState(false);\n const [isComplete, setIsComplete] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [rawText, setRawText] = useState(\"\");\n\n const healerRef = useRef<StreamHealer<T> | null>(null);\n const lastRenderTimeRef = useRef<number>(0);\n const pendingUpdateRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const latestDataRef = useRef<Partial<T> | null>(initialData);\n\n // Initialize or re-create healer instance\n if (!healerRef.current) {\n healerRef.current = new StreamHealer<T>({\n ...parserOptions,\n onUpdate: (partial) => {\n onUpdate?.(partial);\n },\n onError: (err) => {\n setError(err);\n onError?.(err);\n }\n });\n }\n\n // Clean up pending timeouts on unmount\n useEffect(() => {\n return () => {\n if (pendingUpdateRef.current) {\n clearTimeout(pendingUpdateRef.current);\n }\n };\n }, []);\n\n const scheduleRender = useCallback(\n (nextData: Partial<T> | null, raw: string) => {\n latestDataRef.current = nextData;\n setRawText(raw);\n\n if (throttleMs <= 0) {\n setData(nextData);\n return;\n }\n\n const now = performance.now();\n const timeSinceLast = now - lastRenderTimeRef.current;\n\n if (timeSinceLast >= throttleMs) {\n lastRenderTimeRef.current = now;\n setData(nextData);\n } else if (!pendingUpdateRef.current) {\n pendingUpdateRef.current = setTimeout(() => {\n lastRenderTimeRef.current = performance.now();\n setData(latestDataRef.current);\n pendingUpdateRef.current = null;\n }, throttleMs - timeSinceLast);\n }\n },\n [throttleMs]\n );\n\n const feed = useCallback(\n (chunk: string) => {\n if (!healerRef.current) return;\n setIsLoading(true);\n const repaired = healerRef.current.push(chunk);\n scheduleRender(repaired, healerRef.current.getRawBuffer());\n },\n [scheduleRender]\n );\n\n const finish = useCallback(() => {\n if (!healerRef.current) return;\n if (pendingUpdateRef.current) {\n clearTimeout(pendingUpdateRef.current);\n pendingUpdateRef.current = null;\n }\n\n const finalResult = healerRef.current.finish();\n setData(finalResult);\n setRawText(healerRef.current.getRawBuffer());\n setIsLoading(false);\n setIsComplete(true);\n onComplete?.(finalResult);\n }, [onComplete]);\n\n const reset = useCallback(() => {\n if (pendingUpdateRef.current) {\n clearTimeout(pendingUpdateRef.current);\n pendingUpdateRef.current = null;\n }\n healerRef.current?.reset();\n setData(initialData);\n setRawText(\"\");\n setIsLoading(false);\n setIsComplete(false);\n setError(null);\n }, [initialData]);\n\n const pipe = useCallback(\n async (stream: AsyncIterable<string> | ReadableStream<Uint8Array | string>) => {\n reset();\n setIsLoading(true);\n\n try {\n if (\"getReader\" in stream) {\n // It's a Web ReadableStream\n const reader = (stream as ReadableStream).getReader();\n const decoder = new TextDecoder();\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const textChunk = typeof value === \"string\" ? value : decoder.decode(value, { stream: true });\n feed(textChunk);\n }\n } else {\n // It's an AsyncIterable\n for await (const chunk of stream as AsyncIterable<string>) {\n feed(chunk);\n }\n }\n } catch (err) {\n const errorObj = err instanceof Error ? err : new Error(String(err));\n setError(errorObj);\n onError?.(errorObj);\n } finally {\n finish();\n }\n },\n [feed, finish, reset, onError]\n );\n\n return {\n data,\n isLoading,\n isComplete,\n error,\n rawText,\n feed,\n pipe,\n finish,\n reset\n };\n}\n\n"]}
package/dist/react.mjs ADDED
@@ -0,0 +1,6 @@
1
+ import {useState,useRef,useEffect,useCallback}from'react';function N(o,i=true){if(!o||typeof o!="string")return "";let n=o.trim();if(!i)return n;let t=n.indexOf("```");if(t!==-1){let e=n.indexOf(`
2
+ `,t);if(e!==-1)n=n.slice(e+1);else return "";let h=n.lastIndexOf("```");h!==-1&&(n=n.slice(0,h));}let s=n.indexOf("{"),a=n.indexOf("["),l=-1;return s!==-1&&a!==-1?l=Math.min(s,a):s!==-1?l=s:a!==-1&&(l=a),l===-1?"":n.slice(l).trim()}function z(o){let i="",n=false,t=false,s=o.length;for(let a=0;a<s;a++){let l=o[a];if(n){i+=l,t?t=false:l==="\\"?t=true:l==='"'&&(n=false);continue}if(l==='"'){n=true,i+=l;continue}if(l==="/"&&a+1<s&&o[a+1]==="/"){let e=o.indexOf(`
3
+ `,a+2);if(e===-1)break;a=e,i+=`
4
+ `;continue}if(l==="/"&&a+1<s&&o[a+1]==="*"){let e=o.indexOf("*/",a+2);if(e===-1)break;a=e+1;continue}i+=l;}return i}function L(o){let i="",n=false,t=false,s=false;for(let a=0;a<o.length;a++){let l=o[a];if(s){i+=l,s=false;continue}if(l==="\\"){i+=l,s=true;continue}if(l==='"'&&!t){n=!n,i+=l;continue}if(l==="'"&&!n){t=!t,i+='"';continue}i+=l;}return i}function _(o,i){let n=i?.stripMarkdown??true,t=N(o,n);if(!t)return "";t=z(t),t=L(t),t=t.replace(/([:,\\[]\s*)\.(\d+)/g,"$10.$2"),t=t.replace(/([:,\\[]\s*)\+(\d+)/g,"$1$2"),t=t.replace(/,\s*,+/g,","),t=t.replace(/:\s*,/g,": null,"),t=t.replace(/:\s*([a-zA-Z_$][a-zA-Z0-9_$-]*)\s*(?=([,}\]]|$))/g,(f,r)=>{let u=r.toLowerCase();return u==="true"||u==="false"||u==="null"||r==="None"||r==="NaN"||r==="Infinity"||/^t(r(u(e)?)?)?$/i.test(r)||/^f(a(l(s(e)?)?)?)?$/i.test(r)||/^n(u(l(l)?)?)?$/i.test(r)?`: ${r}`:`: "${r}"`}),t=t.replace(/(:\s*"[^",\n\r]*),\s*("([a-zA-Z0-9_$]+)"\s*:)/g,'$1", $2'),t=t.replace(/(\[\s*(?:(?:"[^"]*",\s*)*))"([^",\n\r]*),\s*"/g,'$1"$2", "'),t=t.replace(/\[\s*"\s*\]/g,'[""]'),t=t.replace(/:\s*"\s*([}\]])/g,': ""$1');let s=[],a=false,l=false,e="",h=t.length;for(let f=0;f<h;f++){let r=t[f];if(a){if(l){e+=r,l=false;continue}if(r==="\\"){l=true,e+=r;continue}if(r==='"'){a=false,e+=r;continue}let u=r.charCodeAt(0);if(u<32){r===`
5
+ `?e+="\\n":r==="\r"?e+="\\r":r===" "?e+="\\t":r==="\b"?e+="\\b":r==="\f"?e+="\\f":e+="\\u"+u.toString(16).padStart(4,"0");continue}e+=r;continue}if(r==='"'){let u=e.trimEnd(),p=u[u.length-1];(p==='"'||p==="}"||p==="]"||p==="e"||p==="l")&&(e=u+", "),a=true,e+=r;continue}if(r==="{"||r==="["){let u=e.trimEnd(),p=u[u.length-1];(p==="}"||p==="]"||p==='"')&&(e=u+", "),s.push(r),e+=r;continue}if(r==="}"){if(s.length>0&&s[s.length-1]==="{"&&(s.pop(),e=e.replace(/,\s*$/,""),e+=r,s.length===0))break;continue}if(r==="]"){if(s.length>0&&s[s.length-1]==="["&&(s.pop(),e=e.replace(/,\s*$/,""),e+=r,s.length===0))break;continue}e+=r;}if(a&&(l&&e.endsWith("\\")&&(e=e.slice(0,-1)),e=e.replace(/\\u[0-9a-fA-F]{0,3}$/,""),e+='"'),e=e.trimEnd(),e=e.replace(/([:,\\[]\s*)True\b/g,"$1true"),e=e.replace(/([:,\\[]\s*)False\b/g,"$1false"),e=e.replace(/([:,\\[]\s*)None\b/g,"$1null"),e=e.replace(/([:,\\[]\s*)NaN\b/g,"$1null"),e=e.replace(/([:,\\[]\s*)Infinity\b/g,"$1null"),e=e.replace(/([:,\\[]\s*)-Infinity\b/g,"$1null"),e=e.replace(/:\s*t(r(u(e)?)?)?$/i,": true"),e=e.replace(/:\s*f(a(l(s(e)?)?)?)?$/i,": false"),e=e.replace(/:\s*n(u(l(l)?)?)?$/i,": null"),e=e.replace(/([,[])\s*t(r(u(e)?)?)?$/i,"$1 true"),e=e.replace(/([,[])\s*f(a(l(s(e)?)?)?)?$/i,"$1 false"),e=e.replace(/([,[])\s*n(u(l(l)?)?)?$/i,"$1 null"),e=e.replace(/:\s*(-?\d+)\.(?=\s*([,}\]]|$))/g,": $1.0"),e=e.replace(/([,[])\s*(-?\d+)\.(?=\s*([,}\]]|$))/g,"$1 $2.0"),e=e.replace(/:\s*(-?\d+(\.\d+)?)e[+-]?(?=\s*([,}\]]|$))/gi,": $1"),e=e.replace(/([,[])\s*(-?\d+(\.\d+)?)e[+-]?(?=\s*([,}\]]|$))/gi,"$1 $2"),e=e.replace(/:\s*-(?=\s*([,}\]]|$))/g,": 0"),e=e.replace(/([,[])\s*-(?=\s*([,}\]]|$))/g,"$1 0"),/:\s*$/.test(e)&&(e+="null"),e=e.replace(/,\s*$/,""),e=e.replace(/([{\\,]\s*)([a-zA-Z_$][a-zA-Z0-9_$-]*)\s*:/g,'$1"$2":'),s.length>0&&s[s.length-1]==="{"){let f=e.match(/,\s*"(?:[^"\\]|\\.)*"$/)||e.match(/\{\s*"(?:[^"\\]|\\.)*"$/);if(f){let r=f[0];r.startsWith("{")?e=e.slice(0,e.length-r.length+1):e=e.slice(0,e.length-r.length);}}for(e=e.replace(/,\s*$/,"").trimEnd();s.length>0;){let f=s.pop();e=e.replace(/,\s*$/,"").trimEnd(),f==="{"?e+="}":f==="["&&(e+="]");}return e}function H(o,i){let n=i?.fallback??null;if(!o||typeof o!="string")return n;let t=_(o,{stripMarkdown:i?.stripMarkdown??true});if(!t)return n;try{return JSON.parse(t)}catch(s){if(i?.strict)throw s;return n}}var k=class{buffer="";lastParsed=null;isCompleted=false;options;constructor(i={}){this.options=i,this.lastParsed=i.fallback??null;}push(i){if(this.isCompleted)return this.lastParsed;this.buffer+=i;let n=H(this.buffer,this.options);return n!==null&&(this.lastParsed=n,this.options.onUpdate?.(n)),this.lastParsed}finish(){return this.isCompleted=true,this.lastParsed}getRawBuffer(){return this.buffer}getLatest(){return this.lastParsed}reset(){this.buffer="",this.lastParsed=this.options.fallback??null,this.isCompleted=false;}};function K(o={}){let{throttleMs:i=30,initialData:n=null,onComplete:t,onUpdate:s,onError:a,...l}=o,[e,h]=useState(n),[f,r]=useState(false),[u,p]=useState(false),[I,O]=useState(null),[U,x]=useState(""),m=useRef(null),y=useRef(0),d=useRef(null),A=useRef(n);m.current||(m.current=new k({...l,onUpdate:c=>{s?.(c);},onError:c=>{O(c),a?.(c);}})),useEffect(()=>()=>{d.current&&clearTimeout(d.current);},[]);let v=useCallback((c,g)=>{if(A.current=c,x(g),i<=0){h(c);return}let $=performance.now(),w=$-y.current;w>=i?(y.current=$,h(c)):d.current||(d.current=setTimeout(()=>{y.current=performance.now(),h(A.current),d.current=null;},i-w));},[i]),S=useCallback(c=>{if(!m.current)return;r(true);let g=m.current.push(c);v(g,m.current.getRawBuffer());},[v]),R=useCallback(()=>{if(!m.current)return;d.current&&(clearTimeout(d.current),d.current=null);let c=m.current.finish();h(c),x(m.current.getRawBuffer()),r(false),p(true),t?.(c);},[t]),C=useCallback(()=>{d.current&&(clearTimeout(d.current),d.current=null),m.current?.reset(),h(n),x(""),r(false),p(false),O(null);},[n]),J=useCallback(async c=>{C(),r(true);try{if("getReader"in c){let g=c.getReader(),$=new TextDecoder;for(;;){let{done:w,value:E}=await g.read();if(w)break;let M=typeof E=="string"?E:$.decode(E,{stream:!0});S(M);}}else for await(let g of c)S(g);}catch(g){let $=g instanceof Error?g:new Error(String(g));O($),a?.($);}finally{R();}},[S,R,C,a]);return {data:e,isLoading:f,isComplete:u,error:I,rawText:U,feed:S,pipe:J,finish:R,reset:C}}export{K as useStreamingJson};//# sourceMappingURL=react.mjs.map
6
+ //# sourceMappingURL=react.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/parser.ts","../src/core/stream.ts","../src/react/index.ts"],"names":["extractJsonCandidate","text","stripMarkdown","cleaned","fenceStart","newlineAfterFence","fenceEnd","firstBrace","firstBracket","startIndex","stripComments","input","result","inString","isEscaped","len","i","char","nextNewline","endComment","normalizeSingleQuotes","inDouble","inSingle","repairJson","options","raw","_m","word","lower","stack","code","trimmedSoFar","lastChar","trailingKeyMatch","matchStr","openChar","parseStreamJson","fallback","repaired","error","StreamHealer","chunk","parsed","useStreamingJson","throttleMs","initialData","onComplete","onUpdate","onError","parserOptions","data","setData","useState","isLoading","setIsLoading","isComplete","setIsComplete","setError","rawText","setRawText","healerRef","useRef","lastRenderTimeRef","pendingUpdateRef","latestDataRef","partial","err","useEffect","scheduleRender","useCallback","nextData","now","timeSinceLast","feed","finish","finalResult","reset","pipe","stream","reader","decoder","done","value","textChunk","errorObj"],"mappings":"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","file":"react.mjs","sourcesContent":["/**\n * stream-heal: Zero-dependency, fault-tolerant streaming JSON parser and auto-healer\n * Production-hardened against all real-world LLM streaming edge cases.\n */\n\nexport interface ParseOptions {\n /**\n * If true, automatically strips markdown code fences (e.g. ```json ... ```)\n * and ignores conversational preambles/postambles from LLMs.\n * @default true\n */\n stripMarkdown?: boolean;\n\n /**\n * Fallback value if the JSON cannot be parsed at all yet (e.g., empty stream).\n * @default null\n */\n fallback?: unknown;\n\n /**\n * If true, throws a SyntaxError if healing fails, instead of returning the fallback.\n * @default false\n */\n strict?: boolean;\n}\n\nexport interface RepairOptions {\n /**\n * Strip markdown wrappers like ```json ... ```\n * @default true\n */\n stripMarkdown?: boolean;\n}\n\n/**\n * Extracts and sanitizes the core JSON text from an LLM response stream,\n * stripping markdown codeblock indicators and conversational fluff.\n */\nexport function extractJsonCandidate(text: string, stripMarkdown = true): string {\n if (!text || typeof text !== \"string\") return \"\";\n let cleaned = text.trim();\n\n if (!stripMarkdown) {\n return cleaned;\n }\n\n // Handle markdown code blocks e.g. ```json \\n { ... } \\n ```\n const fenceStart = cleaned.indexOf(\"```\");\n if (fenceStart !== -1) {\n // Find the end of the opening fence line (e.g. ```json\\n)\n const newlineAfterFence = cleaned.indexOf(\"\\n\", fenceStart);\n if (newlineAfterFence !== -1) {\n cleaned = cleaned.slice(newlineAfterFence + 1);\n } else {\n // Stream just started with ```json and no newline yet\n return \"\";\n }\n\n // If there is a closing fence ```, strip everything from it\n const fenceEnd = cleaned.lastIndexOf(\"```\");\n if (fenceEnd !== -1) {\n cleaned = cleaned.slice(0, fenceEnd);\n }\n }\n\n // Find the first opening bracket '{' or '['\n const firstBrace = cleaned.indexOf(\"{\");\n const firstBracket = cleaned.indexOf(\"[\");\n\n let startIndex = -1;\n if (firstBrace !== -1 && firstBracket !== -1) {\n startIndex = Math.min(firstBrace, firstBracket);\n } else if (firstBrace !== -1) {\n startIndex = firstBrace;\n } else if (firstBracket !== -1) {\n startIndex = firstBracket;\n }\n\n if (startIndex === -1) {\n return \"\";\n }\n\n return cleaned.slice(startIndex).trim();\n}\n\n/**\n * Strips JS/C-style single-line and multi-line comments outside of strings.\n */\nfunction stripComments(input: string): string {\n let result = \"\";\n let inString = false;\n let isEscaped = false;\n const len = input.length;\n\n for (let i = 0; i < len; i++) {\n const char = input[i];\n\n if (inString) {\n result += char;\n if (isEscaped) {\n isEscaped = false;\n } else if (char === \"\\\\\") {\n isEscaped = true;\n } else if (char === '\"') {\n inString = false;\n }\n continue;\n }\n\n if (char === '\"') {\n inString = true;\n result += char;\n continue;\n }\n\n // Single-line comment //\n if (char === \"/\" && i + 1 < len && input[i + 1] === \"/\") {\n const nextNewline = input.indexOf(\"\\n\", i + 2);\n if (nextNewline === -1) {\n break; // Remainder of input is a comment\n }\n i = nextNewline; // Skip to newline\n result += \"\\n\";\n continue;\n }\n\n // Multi-line comment /* ... */\n if (char === \"/\" && i + 1 < len && input[i + 1] === \"*\") {\n const endComment = input.indexOf(\"*/\", i + 2);\n if (endComment === -1) {\n break; // Unclosed block comment at stream tail\n }\n i = endComment + 1; // Skip past */\n continue;\n }\n\n result += char;\n }\n\n return result;\n}\n\n/**\n * Normalizes Python/JS single-quoted JSON (e.g. {'name': 'raj'}) to double quotes,\n * while carefully preserving apostrophes inside double-quoted strings (e.g. \"O'Connor\").\n */\nfunction normalizeSingleQuotes(text: string): string {\n let result = \"\";\n let inDouble = false;\n let inSingle = false;\n let isEscaped = false;\n\n for (let i = 0; i < text.length; i++) {\n const char = text[i];\n if (isEscaped) {\n result += char;\n isEscaped = false;\n continue;\n }\n if (char === \"\\\\\") {\n result += char;\n isEscaped = true;\n continue;\n }\n if (char === '\"' && !inSingle) {\n inDouble = !inDouble;\n result += char;\n continue;\n }\n if (char === \"'\" && !inDouble) {\n inSingle = !inSingle;\n result += '\"';\n continue;\n }\n result += char;\n }\n return result;\n}\n\n/**\n * Repairs a truncated or malformed JSON string on the fly, producing a valid JSON string\n * that can be safely parsed by native JSON.parse().\n */\nexport function repairJson(input: string, options?: RepairOptions): string {\n const stripMarkdown = options?.stripMarkdown ?? true;\n let raw = extractJsonCandidate(input, stripMarkdown);\n\n if (!raw) {\n return \"\";\n }\n\n // Strip comments outside strings\n raw = stripComments(raw);\n\n // Normalize Python-style single quotes e.g. {'name': 'raj'} -> {\"name\": \"raj\"}\n raw = normalizeSingleQuotes(raw);\n\n // Fix numbers with leading dots e.g. {\"price\": .50} -> {\"price\": 0.50}\n raw = raw.replace(/([:,\\\\[]\\s*)\\.(\\d+)/g, \"$10.$2\");\n\n // Fix numbers with leading plus e.g. {\"count\": +12} -> {\"count\": 12}\n raw = raw.replace(/([:,\\\\[]\\s*)\\+(\\d+)/g, \"$1$2\");\n\n // Fix consecutive duplicate commas e.g. [1,, 2] -> [1, 2]\n raw = raw.replace(/,\\s*,+/g, \",\");\n\n // Fix empty colon before comma e.g. {\"a\": , \"b\": 1} -> {\"a\": null, \"b\": 1}\n raw = raw.replace(/:\\s*,/g, \": null,\");\n\n // Fix unquoted string values e.g. {\"status\": active, \"role\": admin} -> {\"status\": \"active\", \"role\": \"admin\"}\n raw = raw.replace(\n /:\\s*([a-zA-Z_$][a-zA-Z0-9_$-]*)\\s*(?=([,}\\]]|$))/g,\n (_m, word) => {\n const lower = word.toLowerCase();\n if (\n lower === \"true\" ||\n lower === \"false\" ||\n lower === \"null\" ||\n word === \"None\" ||\n word === \"NaN\" ||\n word === \"Infinity\" ||\n /^t(r(u(e)?)?)?$/i.test(word) ||\n /^f(a(l(s(e)?)?)?)?$/i.test(word) ||\n /^n(u(l(l)?)?)?$/i.test(word)\n ) {\n return `: ${word}`;\n }\n return `: \"${word}\"`;\n }\n );\n\n // Fix unclosed quotes before comma and next key e.g. {\"name\":\"raj, \"age\": 25} -> {\"name\":\"raj\", \"age\": 25}\n raw = raw.replace(/(:\\s*\"[^\",\\n\\r]*),\\s*(\"([a-zA-Z0-9_$]+)\"\\s*:)/g, '$1\", $2');\n\n // Fix missing closing quotes in arrays e.g. [\"cricket, \"racing\"] -> [\"cricket\", \"racing\"]\n raw = raw.replace(/(\\[\\s*(?:(?:\"[^\"]*\",\\s*)*))\"([^\",\\n\\r]*),\\s*\"/g, '$1\"$2\", \"');\n\n // Fix stray quotes immediately preceding container closing e.g. [\"] -> [\"\"] or {\"key\": \"} -> {\"key\": \"\"}\n raw = raw.replace(/\\[\\s*\"\\s*\\]/g, '[\"\"]');\n raw = raw.replace(/:\\s*\"\\s*([}\\]])/g, ': \"\"$1');\n\n const stack: Array<\"{\" | \"[\"> = [];\n let inString = false;\n let isEscaped = false;\n let result = \"\";\n const len = raw.length;\n\n for (let i = 0; i < len; i++) {\n const char = raw[i];\n\n if (inString) {\n if (isEscaped) {\n result += char;\n isEscaped = false;\n continue;\n }\n\n if (char === \"\\\\\") {\n isEscaped = true;\n result += char;\n continue;\n }\n\n if (char === '\"') {\n inString = false;\n result += char;\n continue;\n }\n\n // Handle all unescaped control characters in strings (ASCII < 32, forbidden by JSON spec)\n const code = char.charCodeAt(0);\n if (code < 32) {\n if (char === \"\\n\") {\n result += \"\\\\n\";\n } else if (char === \"\\r\") {\n result += \"\\\\r\";\n } else if (char === \"\\t\") {\n result += \"\\\\t\";\n } else if (char === \"\\b\") {\n result += \"\\\\b\";\n } else if (char === \"\\f\") {\n result += \"\\\\f\";\n } else {\n result += \"\\\\u\" + code.toString(16).padStart(4, \"0\");\n }\n continue;\n }\n\n result += char;\n continue;\n }\n\n // --- NOT IN STRING ---\n\n if (char === '\"') {\n // Check for missing comma before this string key/value if preceded by another value\n const trimmedSoFar = result.trimEnd();\n const lastChar = trimmedSoFar[trimmedSoFar.length - 1];\n if (\n lastChar === '\"' ||\n lastChar === \"}\" ||\n lastChar === \"]\" ||\n lastChar === \"e\" || // from true\n lastChar === \"l\" // from null\n ) {\n result = trimmedSoFar + \", \";\n }\n\n inString = true;\n result += char;\n continue;\n }\n\n if (char === \"{\" || char === \"[\") {\n // Check for missing comma before nested container\n const trimmedSoFar = result.trimEnd();\n const lastChar = trimmedSoFar[trimmedSoFar.length - 1];\n if (lastChar === \"}\" || lastChar === \"]\" || lastChar === '\"') {\n result = trimmedSoFar + \", \";\n }\n\n stack.push(char);\n result += char;\n continue;\n }\n\n if (char === \"}\") {\n if (stack.length > 0 && stack[stack.length - 1] === \"{\") {\n stack.pop();\n result = result.replace(/,\\s*$/, \"\");\n result += char;\n // If the root container has closed, stop to ignore trailing conversational postambles\n if (stack.length === 0) {\n break;\n }\n }\n continue;\n }\n\n if (char === \"]\") {\n if (stack.length > 0 && stack[stack.length - 1] === \"[\") {\n stack.pop();\n result = result.replace(/,\\s*$/, \"\");\n result += char;\n // If the root container has closed, stop to ignore trailing conversational postambles\n if (stack.length === 0) {\n break;\n }\n }\n continue;\n }\n\n result += char;\n }\n\n // --- STREAM REPAIR HEURISTICS ---\n\n // 1. If currently inside a string\n if (inString) {\n if (isEscaped) {\n // Remove trailing dangling backslash e.g. \"path\\\": \\\"C:\\\\\n if (result.endsWith(\"\\\\\")) {\n result = result.slice(0, -1);\n }\n }\n // Handle incomplete Unicode escape sequences at end of string e.g. \\u, \\u0, \\u00, \\u004\n result = result.replace(/\\\\u[0-9a-fA-F]{0,3}$/, \"\");\n\n // Close the unclosed string (preserves literal content like code brackets, regexes, math)\n result += '\"';\n }\n\n // 2. Trim trailing whitespaces\n result = result.trimEnd();\n\n // 3. Normalize Python-style constants emitted by models: True, False, None, NaN, Infinity\n result = result.replace(/([:,\\\\[]\\s*)True\\b/g, \"$1true\");\n result = result.replace(/([:,\\\\[]\\s*)False\\b/g, \"$1false\");\n result = result.replace(/([:,\\\\[]\\s*)None\\b/g, \"$1null\");\n result = result.replace(/([:,\\\\[]\\s*)NaN\\b/g, \"$1null\");\n result = result.replace(/([:,\\\\[]\\s*)Infinity\\b/g, \"$1null\");\n result = result.replace(/([:,\\\\[]\\s*)-Infinity\\b/g, \"$1null\");\n\n // 4. Fix incomplete primitives at the end of the stream\n // Object values:\n result = result.replace(/:\\s*t(r(u(e)?)?)?$/i, \": true\");\n result = result.replace(/:\\s*f(a(l(s(e)?)?)?)?$/i, \": false\");\n result = result.replace(/:\\s*n(u(l(l)?)?)?$/i, \": null\");\n\n // Array items:\n result = result.replace(/([,[])\\s*t(r(u(e)?)?)?$/i, \"$1 true\");\n result = result.replace(/([,[])\\s*f(a(l(s(e)?)?)?)?$/i, \"$1 false\");\n result = result.replace(/([,[])\\s*n(u(l(l)?)?)?$/i, \"$1 null\");\n\n // Fix incomplete numbers:\n // Decimals ending with dot e.g. \"price\": 12. -> 12.0\n result = result.replace(/:\\s*(-?\\d+)\\.(?=\\s*([,}\\]]|$))/g, \": $1.0\");\n result = result.replace(/([,[])\\s*(-?\\d+)\\.(?=\\s*([,}\\]]|$))/g, \"$1 $2.0\");\n\n // Exponents in flight e.g. 1.2e, 1.2e+, 1.2e-\n result = result.replace(/:\\s*(-?\\d+(\\.\\d+)?)e[+-]?(?=\\s*([,}\\]]|$))/gi, \": $1\");\n result = result.replace(/([,[])\\s*(-?\\d+(\\.\\d+)?)e[+-]?(?=\\s*([,}\\]]|$))/gi, \"$1 $2\");\n\n // Lone minus signs e.g. \"count\": - -> 0\n result = result.replace(/:\\s*-(?=\\s*([,}\\]]|$))/g, \": 0\");\n result = result.replace(/([,[])\\s*-(?=\\s*([,}\\]]|$))/g, \"$1 0\");\n\n // 5. Clean trailing unfulfilled tokens\n // If result ends with a colon e.g. `{\"key\": `\n if (/:\\s*$/.test(result)) {\n result += \"null\";\n }\n\n // If result ends with a trailing comma e.g. `[1, 2, ` or `{\"a\": 1, `\n result = result.replace(/,\\s*$/, \"\");\n\n // Auto-quote unquoted keys emitted by open-source models e.g. {name: \"Alex\", count: 10}\n result = result.replace(/([{\\\\,]\\s*)([a-zA-Z_$][a-zA-Z0-9_$-]*)\\s*:/g, '$1\"$2\":');\n\n // If an object ended with an unfinished key without colon (e.g. `{\"key\"` or `{\"key\\\"name\"`)\n if (stack.length > 0 && stack[stack.length - 1] === \"{\") {\n const trailingKeyMatch =\n result.match(/,\\s*\"(?:[^\"\\\\]|\\\\.)*\"$/) ||\n result.match(/\\{\\s*\"(?:[^\"\\\\]|\\\\.)*\"$/);\n if (trailingKeyMatch) {\n const matchStr = trailingKeyMatch[0];\n if (matchStr.startsWith(\"{\")) {\n result = result.slice(0, result.length - matchStr.length + 1); // keep {\n } else {\n result = result.slice(0, result.length - matchStr.length);\n }\n }\n }\n\n // Re-clean trailing commas\n result = result.replace(/,\\s*$/, \"\").trimEnd();\n\n // 6. Close all unclosed brackets/braces in reverse order\n while (stack.length > 0) {\n const openChar = stack.pop();\n result = result.replace(/,\\s*$/, \"\").trimEnd();\n if (openChar === \"{\") {\n result += \"}\";\n } else if (openChar === \"[\") {\n result += \"]\";\n }\n }\n\n return result;\n}\n\n/**\n * Parses a streaming or partial JSON string into a strongly-typed JavaScript object.\n * Safe against incomplete chunks, never crashes, auto-repairs truncated streams.\n *\n * @example\n * ```ts\n * const partial = parseStreamJson<{ name: string; age: number }>('{\"name\": \"Alex\", \"age\": 2');\n * console.log(partial); // { name: \"Alex\", age: 2 }\n * ```\n */\nexport function parseStreamJson<T = unknown>(\n input: string,\n options?: ParseOptions\n): Partial<T> | null {\n const fallback = (options?.fallback ?? null) as Partial<T> | null;\n\n if (!input || typeof input !== \"string\") {\n return fallback;\n }\n\n const repaired = repairJson(input, {\n stripMarkdown: options?.stripMarkdown ?? true\n });\n\n if (!repaired) {\n return fallback;\n }\n\n try {\n return JSON.parse(repaired) as Partial<T>;\n } catch (error) {\n if (options?.strict) {\n throw error;\n }\n return fallback;\n }\n}\n","import { parseStreamJson, ParseOptions } from \"./parser\";\n\nexport interface StreamHealerOptions extends ParseOptions {\n /**\n * Callback fired whenever new valid partial JSON state is resolved.\n */\n onUpdate?: (partial: unknown) => void;\n /**\n * Callback fired if an unrecoverable syntax error occurs in strict mode.\n */\n onError?: (error: Error) => void;\n}\n\n/**\n * Stateful streaming controller for incrementally receiving chunks from LLMs.\n */\nexport class StreamHealer<T = unknown> {\n private buffer = \"\";\n private lastParsed: Partial<T> | null = null;\n private isCompleted = false;\n private options: StreamHealerOptions;\n\n constructor(options: StreamHealerOptions = {}) {\n this.options = options;\n this.lastParsed = (options.fallback ?? null) as Partial<T> | null;\n }\n\n /**\n * Appends an incremental text chunk to the stream and returns the latest repaired partial object.\n */\n public push(chunk: string): Partial<T> | null {\n if (this.isCompleted) {\n return this.lastParsed;\n }\n\n this.buffer += chunk;\n const parsed = parseStreamJson<T>(this.buffer, this.options);\n\n if (parsed !== null) {\n this.lastParsed = parsed;\n this.options.onUpdate?.(parsed);\n }\n\n return this.lastParsed;\n }\n\n /**\n * Signals that the stream has finished. Returns the final parsed object.\n */\n public finish(): Partial<T> | null {\n this.isCompleted = true;\n return this.lastParsed;\n }\n\n /**\n * Returns the raw accumulated string buffer so far.\n */\n public getRawBuffer(): string {\n return this.buffer;\n }\n\n /**\n * Returns the latest successfully repaired partial object.\n */\n public getLatest(): Partial<T> | null {\n return this.lastParsed;\n }\n\n /**\n * Resets the healer state for a new stream.\n */\n public reset(): void {\n this.buffer = \"\";\n this.lastParsed = (this.options.fallback ?? null) as Partial<T> | null;\n this.isCompleted = false;\n }\n}\n\n/**\n * Creates a stateful StreamHealer instance.\n */\nexport function createStreamHealer<T = unknown>(options?: StreamHealerOptions): StreamHealer<T> {\n return new StreamHealer<T>(options);\n}\n\n/**\n * Web Standard TransformStream compatible with Fetch API response.body.\n *\n * @example\n * ```ts\n * const response = await fetch('/api/llm-stream');\n * const jsonStream = response.body\n * .pipeThrough(new TextDecoderStream())\n * .pipeThrough(createHealTransformStream<MySchema>());\n * ```\n */\nexport function createHealTransformStream<T = unknown>(\n options?: StreamHealerOptions\n): TransformStream<string, Partial<T>> {\n const healer = new StreamHealer<T>(options);\n\n return new TransformStream<string, Partial<T>>({\n transform(chunk, controller) {\n const repaired = healer.push(chunk);\n if (repaired !== null) {\n controller.enqueue(repaired);\n }\n },\n flush(controller) {\n const finalResult = healer.finish();\n if (finalResult !== null) {\n controller.enqueue(finalResult);\n }\n }\n });\n}\n\n/**\n * Consumes an AsyncIterable of string chunks (such as from OpenAI SDK, Anthropic, or Ollama)\n * and yields the repaired partial JSON objects in real-time.\n *\n * @example\n * ```ts\n * for await (const user of healAsyncIterable<User>(openaiStream)) {\n * renderCard(user.name, user.bio);\n * }\n * ```\n */\nexport async function* healAsyncIterable<T = unknown>(\n stream: AsyncIterable<string>,\n options?: StreamHealerOptions\n): AsyncGenerator<Partial<T>, void, unknown> {\n const healer = new StreamHealer<T>(options);\n\n for await (const chunk of stream) {\n const partial = healer.push(chunk);\n if (partial !== null) {\n yield partial;\n }\n }\n\n const finalObj = healer.finish();\n if (finalObj !== null) {\n yield finalObj;\n }\n}\n\n","import { useState, useRef, useCallback, useEffect } from \"react\";\nimport { StreamHealer, StreamHealerOptions } from \"../core/stream\";\n\nexport interface UseStreamingJsonOptions<T> extends StreamHealerOptions {\n /**\n * Throttle UI re-renders in milliseconds to prevent React frame drops\n * during ultra-fast LLM streams (e.g., Groq, Cerebras).\n * Set to 0 to disable throttling.\n * @default 30\n */\n throttleMs?: number;\n\n /**\n * Initial data before the stream begins.\n */\n initialData?: Partial<T> | null;\n\n /**\n * Callback fired when stream finishes.\n */\n onComplete?: (finalData: Partial<T> | null) => void;\n}\n\nexport interface UseStreamingJsonReturn<T> {\n /**\n * The current repaired, valid partial JSON object.\n */\n data: Partial<T> | null;\n\n /**\n * True if a stream is actively in progress.\n */\n isLoading: boolean;\n\n /**\n * True if the stream has cleanly completed.\n */\n isComplete: boolean;\n\n /**\n * Any stream error encountered.\n */\n error: Error | null;\n\n /**\n * The full unparsed raw text buffer accumulated so far.\n */\n rawText: string;\n\n /**\n * Manually push an incoming chunk into the stream.\n */\n feed: (chunk: string) => void;\n\n /**\n * Pipe an async iterable (e.g. OpenAI stream) directly into the hook.\n */\n pipe: (stream: AsyncIterable<string> | ReadableStream<Uint8Array | string>) => Promise<void>;\n\n /**\n * Manually signal that the stream has finished.\n */\n finish: () => void;\n\n /**\n * Resets the hook to its initial idle state.\n */\n reset: () => void;\n}\n\n/**\n * React hook for consuming streaming LLM JSON responses with zero-lag UI updates and auto-healing.\n *\n * @example\n * ```tsx\n * const { data, isLoading, pipe } = useStreamingJson<UserForm>();\n *\n * const handleSubmit = async () => {\n * const response = await fetch('/api/ai');\n * pipe(response.body!);\n * };\n *\n * return (\n * <div>\n * <h2>{data?.name || \"Generating...\"}</h2>\n * <p>{data?.bio}</p>\n * </div>\n * );\n * ```\n */\nexport function useStreamingJson<T = unknown>(\n options: UseStreamingJsonOptions<T> = {}\n): UseStreamingJsonReturn<T> {\n const {\n throttleMs = 30,\n initialData = null,\n onComplete,\n onUpdate,\n onError,\n ...parserOptions\n } = options;\n\n const [data, setData] = useState<Partial<T> | null>(initialData);\n const [isLoading, setIsLoading] = useState(false);\n const [isComplete, setIsComplete] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [rawText, setRawText] = useState(\"\");\n\n const healerRef = useRef<StreamHealer<T> | null>(null);\n const lastRenderTimeRef = useRef<number>(0);\n const pendingUpdateRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const latestDataRef = useRef<Partial<T> | null>(initialData);\n\n // Initialize or re-create healer instance\n if (!healerRef.current) {\n healerRef.current = new StreamHealer<T>({\n ...parserOptions,\n onUpdate: (partial) => {\n onUpdate?.(partial);\n },\n onError: (err) => {\n setError(err);\n onError?.(err);\n }\n });\n }\n\n // Clean up pending timeouts on unmount\n useEffect(() => {\n return () => {\n if (pendingUpdateRef.current) {\n clearTimeout(pendingUpdateRef.current);\n }\n };\n }, []);\n\n const scheduleRender = useCallback(\n (nextData: Partial<T> | null, raw: string) => {\n latestDataRef.current = nextData;\n setRawText(raw);\n\n if (throttleMs <= 0) {\n setData(nextData);\n return;\n }\n\n const now = performance.now();\n const timeSinceLast = now - lastRenderTimeRef.current;\n\n if (timeSinceLast >= throttleMs) {\n lastRenderTimeRef.current = now;\n setData(nextData);\n } else if (!pendingUpdateRef.current) {\n pendingUpdateRef.current = setTimeout(() => {\n lastRenderTimeRef.current = performance.now();\n setData(latestDataRef.current);\n pendingUpdateRef.current = null;\n }, throttleMs - timeSinceLast);\n }\n },\n [throttleMs]\n );\n\n const feed = useCallback(\n (chunk: string) => {\n if (!healerRef.current) return;\n setIsLoading(true);\n const repaired = healerRef.current.push(chunk);\n scheduleRender(repaired, healerRef.current.getRawBuffer());\n },\n [scheduleRender]\n );\n\n const finish = useCallback(() => {\n if (!healerRef.current) return;\n if (pendingUpdateRef.current) {\n clearTimeout(pendingUpdateRef.current);\n pendingUpdateRef.current = null;\n }\n\n const finalResult = healerRef.current.finish();\n setData(finalResult);\n setRawText(healerRef.current.getRawBuffer());\n setIsLoading(false);\n setIsComplete(true);\n onComplete?.(finalResult);\n }, [onComplete]);\n\n const reset = useCallback(() => {\n if (pendingUpdateRef.current) {\n clearTimeout(pendingUpdateRef.current);\n pendingUpdateRef.current = null;\n }\n healerRef.current?.reset();\n setData(initialData);\n setRawText(\"\");\n setIsLoading(false);\n setIsComplete(false);\n setError(null);\n }, [initialData]);\n\n const pipe = useCallback(\n async (stream: AsyncIterable<string> | ReadableStream<Uint8Array | string>) => {\n reset();\n setIsLoading(true);\n\n try {\n if (\"getReader\" in stream) {\n // It's a Web ReadableStream\n const reader = (stream as ReadableStream).getReader();\n const decoder = new TextDecoder();\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const textChunk = typeof value === \"string\" ? value : decoder.decode(value, { stream: true });\n feed(textChunk);\n }\n } else {\n // It's an AsyncIterable\n for await (const chunk of stream as AsyncIterable<string>) {\n feed(chunk);\n }\n }\n } catch (err) {\n const errorObj = err instanceof Error ? err : new Error(String(err));\n setError(errorObj);\n onError?.(errorObj);\n } finally {\n finish();\n }\n },\n [feed, finish, reset, onError]\n );\n\n return {\n data,\n isLoading,\n isComplete,\n error,\n rawText,\n feed,\n pipe,\n finish,\n reset\n };\n}\n\n"]}
package/package.json CHANGED
@@ -1,6 +1,75 @@
1
1
  {
2
2
  "name": "stream-heal",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "Zero-dependency, fault-tolerant streaming JSON parser and auto-healer for LLM outputs and generative UI",
5
+ "keywords": [
6
+ "ai",
7
+ "llm",
8
+ "stream",
9
+ "json",
10
+ "partial-json",
11
+ "generative-ui",
12
+ "openai",
13
+ "anthropic",
14
+ "react",
15
+ "streaming-json",
16
+ "parser"
17
+ ],
18
+ "author": "Fahad Shaikh <fahadshaikh6160@gmail.com> (https://github.com/Fahad6160)",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/Fahad6160/stream-heal.git"
22
+ },
23
+ "bugs": {
24
+ "url": "https://github.com/Fahad6160/stream-heal/issues"
25
+ },
26
+ "homepage": "https://github.com/Fahad6160/stream-heal#readme",
27
+ "license": "MIT",
28
+ "main": "./dist/index.js",
29
+ "module": "./dist/index.mjs",
30
+ "types": "./dist/index.d.ts",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.ts",
34
+ "import": "./dist/index.mjs",
35
+ "require": "./dist/index.js"
36
+ },
37
+ "./react": {
38
+ "types": "./dist/react.d.ts",
39
+ "import": "./dist/react.mjs",
40
+ "require": "./dist/react.js"
41
+ }
42
+ },
43
+ "files": [
44
+ "dist",
45
+ "README.md",
46
+ "LICENSE"
47
+ ],
48
+ "scripts": {
49
+ "build": "tsup",
50
+ "test": "vitest run",
51
+ "test:watch": "vitest",
52
+ "test:coverage": "vitest run --coverage",
53
+ "bench": "tsx benchmarks/bench.ts",
54
+ "lint": "tsc --noEmit",
55
+ "prepublishOnly": "npm run build && npm run test"
56
+ },
57
+ "peerDependencies": {
58
+ "react": ">=18.0.0"
59
+ },
60
+ "peerDependenciesMeta": {
61
+ "react": {
62
+ "optional": true
63
+ }
64
+ },
65
+ "devDependencies": {
66
+ "@types/node": "^22.0.0",
67
+ "@types/react": "^18.3.0",
68
+ "react": "^18.3.0",
69
+ "tsup": "^8.3.0",
70
+ "tsx": "^4.19.0",
71
+ "typescript": "^5.6.0",
72
+ "vitest": "^2.1.0"
73
+ }
74
+ }
75
+