stream-heal 0.0.0-stage → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +251 -2
- package/dist/index.d.mts +121 -0
- package/dist/index.d.ts +121 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +6 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react.d.mts +80 -0
- package/dist/react.d.ts +80 -0
- package/dist/react.js +6 -0
- package/dist/react.js.map +1 -0
- package/dist/react.mjs +6 -0
- package/dist/react.mjs.map +1 -0
- package/package.json +72 -4
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,252 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 🩹 stream-heal
|
|
2
|
+
|
|
3
|
+
> **Zero-dependency, fault-tolerant streaming JSON parser and auto-healer for LLM outputs and generative UI.**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/stream-heal)
|
|
6
|
+
[](https://bundlephobia.com/package/stream-heal)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](package.json)
|
|
9
|
+
[](tsconfig.json)
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<img src="https://raw.githubusercontent.com/Fahad6160/stream-heal/main/demo/demo.gif" alt="stream-heal real-time demo vs standard JSON.parse" width="100%" />
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
### 💡 The 5-Second Mental Model
|
|
18
|
+
|
|
19
|
+
> **Think of `stream-heal` as `JSON.parse()`, but built for streaming AI.**
|
|
20
|
+
> When an LLM outputs JSON token-by-token, standard `JSON.parse()` crashes on every single chunk with `SyntaxError: Unexpected end of JSON`.
|
|
21
|
+
> `stream-heal` fixes the brackets, strings, and commas on the fly so your app gets a **clean, valid JavaScript object at every single frame**.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## ⚡ The Problem
|
|
26
|
+
|
|
27
|
+
When OpenAI, Claude, Gemini, or Ollama streams JSON, each chunk arrives half-finished:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{"user": {"name": "Alex", "skills": ["TypeScript", "Re
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
If you call `JSON.parse()` on this, your app crashes:
|
|
34
|
+
```
|
|
35
|
+
💥 SyntaxError: Unexpected end of JSON input
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Most developers either:
|
|
39
|
+
1. Wait 5 to 10 seconds for the entire stream to finish (ruining the fast AI experience).
|
|
40
|
+
2. Write messy regex hacks that freeze the user's browser.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## ✨ The Solution
|
|
45
|
+
|
|
46
|
+
`stream-heal` auto-heals incomplete tokens **in 5.5 microseconds** with **0 external dependencies** and a **sub-2kB** footprint.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { parseStreamJson } from "stream-heal";
|
|
50
|
+
|
|
51
|
+
// Incomplete chunk while the AI is still typing:
|
|
52
|
+
const chunk = '{"name": "Alex", "active": tr';
|
|
53
|
+
|
|
54
|
+
const data = parseStreamJson(chunk);
|
|
55
|
+
console.log(data);
|
|
56
|
+
// Output: { name: "Alex", active: true } <-- Valid, safe, never crashes!
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 🛠️ What `stream-heal` Fixes Automatically
|
|
62
|
+
|
|
63
|
+
| What the AI outputs mid-stream | What `stream-heal` produces |
|
|
64
|
+
| :--- | :--- |
|
|
65
|
+
| **Unclosed strings:** `{"title": "AI Roadmap` | `{"title": "AI Roadmap"}` |
|
|
66
|
+
| **Missing quotes in arrays:** `["cricket, "racing"` | `["cricket", "racing"]` |
|
|
67
|
+
| **Markdown wrappers:** ` ```json\n{"id": 1}\n``` ` | `{"id": 1}` |
|
|
68
|
+
| **Partial booleans/null:** `{"flag": tr` | `{"flag": true}` |
|
|
69
|
+
| **Trailing decimals:** `{"price": 49.` | `{"price": 49.0}` |
|
|
70
|
+
| **Leading decimals:** `{"price": .50}` | `{"price": 0.50}` |
|
|
71
|
+
| **Dangling commas:** `{"items": [1, 2, ]` | `{"items": [1, 2]}` |
|
|
72
|
+
| **Missing commas:** `{"a": 1 "b": 2}` | `{"a": 1, "b": 2}` |
|
|
73
|
+
| **Single quotes:** `{'name': 'raj'}` | `{"name": "raj"}` |
|
|
74
|
+
| **Python casing:** `{"active": True, "data": None}` | `{"active": true, "data": null}` |
|
|
75
|
+
| **Unquoted values:** `{"status": active}` | `{"status": "active"}` |
|
|
76
|
+
| **Code & template strings:** `{"code": "if (x) { run(); }` | `{"code": "if (x) { run(); }"}` (preserves braces & regexes without flicker) |
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 📦 Installation
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npm install stream-heal
|
|
84
|
+
# or
|
|
85
|
+
pnpm add stream-heal
|
|
86
|
+
# or
|
|
87
|
+
yarn add stream-heal
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 🚀 Quick Start
|
|
93
|
+
|
|
94
|
+
### 1. Simple One-Liner Stream Parsing
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { parseStreamJson } from "stream-heal";
|
|
98
|
+
|
|
99
|
+
// Incomplete JSON from an LLM stream:
|
|
100
|
+
const chunk = '{"title": "AI Roadmap", "tags": ["agent", "web';
|
|
101
|
+
|
|
102
|
+
const data = parseStreamJson<{ title: string; tags: string[] }>(chunk);
|
|
103
|
+
console.log(data);
|
|
104
|
+
// Output: { title: "AI Roadmap", tags: ["agent", "web"] }
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
### 2. Stream with OpenAI / Anthropic / Ollama
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import { healAsyncIterable } from "stream-heal";
|
|
113
|
+
import OpenAI from "openai";
|
|
114
|
+
|
|
115
|
+
const openai = new OpenAI();
|
|
116
|
+
|
|
117
|
+
const stream = await openai.chat.completions.create({
|
|
118
|
+
model: "gpt-4o",
|
|
119
|
+
messages: [{ role: "user", content: "Generate project stats in JSON" }],
|
|
120
|
+
stream: true,
|
|
121
|
+
response_format: { type: "json_object" }
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
async function* tokenGenerator() {
|
|
125
|
+
for await (const chunk of stream) {
|
|
126
|
+
yield chunk.choices[0]?.delta?.content || "";
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Consumes the stream token-by-token with zero crashes:
|
|
131
|
+
for await (const partial of healAsyncIterable<ProjectStats>(tokenGenerator())) {
|
|
132
|
+
console.log("Current state:", partial);
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### 3. React Hook with Built-in Throttling (`stream-heal/react`)
|
|
139
|
+
|
|
140
|
+
Prevent UI freezes during high-speed streams (e.g., Groq, Cerebras, 100+ tokens/sec) using automatic frame batching:
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
import React from "react";
|
|
144
|
+
import { useStreamingJson } from "stream-heal/react";
|
|
145
|
+
|
|
146
|
+
interface ProductCard {
|
|
147
|
+
name: string;
|
|
148
|
+
price: number;
|
|
149
|
+
features: string[];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function LiveProductCard() {
|
|
153
|
+
const { data, isLoading, pipe } = useStreamingJson<ProductCard>({
|
|
154
|
+
throttleMs: 30 // Updates smoothly at ~33fps
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const handleGenerate = async () => {
|
|
158
|
+
const response = await fetch("/api/generate");
|
|
159
|
+
if (response.body) {
|
|
160
|
+
pipe(response.body); // Directly pipe Fetch response stream!
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<div>
|
|
166
|
+
<button onClick={handleGenerate} disabled={isLoading}>
|
|
167
|
+
{isLoading ? "Streaming..." : "Generate Product"}
|
|
168
|
+
</button>
|
|
169
|
+
|
|
170
|
+
<h2>{data?.name || "Generating title..."}</h2>
|
|
171
|
+
<p>Price: ${data?.price ?? "--"}</p>
|
|
172
|
+
<ul>
|
|
173
|
+
{data?.features?.map((f, i) => (
|
|
174
|
+
<li key={i}>{f}</li>
|
|
175
|
+
))}
|
|
176
|
+
</ul>
|
|
177
|
+
</div>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
### 4. Web Standard TransformStream (Edge / Cloudflare / Node)
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
import { createHealTransformStream } from "stream-heal";
|
|
188
|
+
|
|
189
|
+
const response = await fetch("https://api.example.com/llm-stream");
|
|
190
|
+
|
|
191
|
+
const healedJsonStream = response.body
|
|
192
|
+
.pipeThrough(new TextDecoderStream())
|
|
193
|
+
.pipeThrough(createHealTransformStream<MyData>());
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## 📊 Real Benchmarks
|
|
199
|
+
|
|
200
|
+
Benchmarked on **50,000 partial chunks** simulating token-by-token LLM generation:
|
|
201
|
+
|
|
202
|
+
| Metric | Result |
|
|
203
|
+
| :--- | :--- |
|
|
204
|
+
| **Average Latency** | **5.56 µs** (microseconds!) per chunk |
|
|
205
|
+
| **Throughput** | **~180,000 ops / sec** |
|
|
206
|
+
| **Total Test Operations** | 50,000 operations in 278 ms |
|
|
207
|
+
| **Crashes / Errors** | **0** (100% fault-tolerant) |
|
|
208
|
+
| **Bundle Size** | **< 2 kB** minzipped |
|
|
209
|
+
| **Dependencies** | **0** |
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 🛠️ API Reference
|
|
214
|
+
|
|
215
|
+
### `parseStreamJson<T>(input: string, options?: ParseOptions): Partial<T> | null`
|
|
216
|
+
Parses and auto-heals an incomplete JSON string into a strongly-typed partial object.
|
|
217
|
+
|
|
218
|
+
### `repairJson(input: string, options?: RepairOptions): string`
|
|
219
|
+
Returns the repaired raw JSON string ready for `JSON.parse()`.
|
|
220
|
+
|
|
221
|
+
### `createStreamHealer<T>(options?: StreamHealerOptions): StreamHealer<T>`
|
|
222
|
+
Creates an incremental, stateful stream tracker.
|
|
223
|
+
|
|
224
|
+
### `useStreamingJson<T>(options?: UseStreamingJsonOptions<T>)`
|
|
225
|
+
React hook providing `{ data, isLoading, isComplete, pipe, feed, reset }`.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 🤝 Contributing
|
|
230
|
+
|
|
231
|
+
Contributions, issues, and feature requests are welcome!
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
# Clone the repository
|
|
235
|
+
git clone https://github.com/Fahad6160/stream-heal.git
|
|
236
|
+
|
|
237
|
+
# Install dependencies
|
|
238
|
+
npm install
|
|
239
|
+
|
|
240
|
+
# Run test suite
|
|
241
|
+
npm test
|
|
242
|
+
|
|
243
|
+
# Run benchmark
|
|
244
|
+
npm run bench
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## 📄 License
|
|
250
|
+
|
|
251
|
+
MIT © 2026 [Fahad Shaikh](https://github.com/Fahad6160). Built for the modern AI web developer.
|
|
2
252
|
|
|
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.
|
package/dist/index.d.mts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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"]}
|
package/dist/react.d.mts
ADDED
|
@@ -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 };
|
package/dist/react.d.ts
ADDED
|
@@ -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":"0DAsCO,SAASA,EAAqBC,CAAAA,CAAcC,CAAAA,CAAgB,KAAc,CAC/E,GAAI,CAACD,CAAAA,EAAQ,OAAOA,CAAAA,EAAS,QAAA,CAAU,OAAO,EAAA,CAC9C,IAAIE,CAAAA,CAAUF,CAAAA,CAAK,MAAK,CAExB,GAAI,CAACC,CAAAA,CACH,OAAOC,EAIT,IAAMC,CAAAA,CAAaD,EAAQ,OAAA,CAAQ,KAAK,EACxC,GAAIC,CAAAA,GAAe,GAAI,CAErB,IAAMC,CAAAA,CAAoBF,CAAAA,CAAQ,OAAA,CAAQ;AAAA,CAAA,CAAMC,CAAU,CAAA,CAC1D,GAAIC,CAAAA,GAAsB,EAAA,CACxBF,CAAAA,CAAUA,CAAAA,CAAQ,KAAA,CAAME,CAAAA,CAAoB,CAAC,CAAA,CAAA,KAG7C,OAAO,GAIT,IAAMC,CAAAA,CAAWH,CAAAA,CAAQ,WAAA,CAAY,KAAK,CAAA,CACtCG,CAAAA,GAAa,EAAA,GACfH,EAAUA,CAAAA,CAAQ,KAAA,CAAM,CAAA,CAAGG,CAAQ,CAAA,EAEvC,CAGA,IAAMC,CAAAA,CAAaJ,EAAQ,OAAA,CAAQ,GAAG,CAAA,CAChCK,CAAAA,CAAeL,CAAAA,CAAQ,OAAA,CAAQ,GAAG,CAAA,CAEpCM,EAAa,EAAA,CASjB,OARIF,CAAAA,GAAe,EAAA,EAAMC,CAAAA,GAAiB,EAAA,CACxCC,CAAAA,CAAa,IAAA,CAAK,IAAIF,CAAAA,CAAYC,CAAY,CAAA,CACrCD,CAAAA,GAAe,EAAA,CACxBE,CAAAA,CAAaF,CAAAA,CACJC,CAAAA,GAAiB,KAC1BC,CAAAA,CAAaD,CAAAA,CAAAA,CAGXC,CAAAA,GAAe,EAAA,CACV,EAAA,CAGFN,CAAAA,CAAQ,KAAA,CAAMM,CAAU,EAAE,IAAA,EACnC,CAKA,SAASC,CAAAA,CAAcC,CAAAA,CAAuB,CAC5C,IAAIC,EAAS,EAAA,CACTC,CAAAA,CAAW,KAAA,CACXC,CAAAA,CAAY,KAAA,CACVC,CAAAA,CAAMJ,CAAAA,CAAM,MAAA,CAElB,QAASK,CAAAA,CAAI,CAAA,CAAGA,CAAAA,CAAID,CAAAA,CAAKC,CAAAA,EAAAA,CAAK,CAC5B,IAAMC,CAAAA,CAAON,EAAMK,CAAC,CAAA,CAEpB,GAAIH,CAAAA,CAAU,CACZD,CAAAA,EAAUK,CAAAA,CACNH,CAAAA,CACFA,EAAY,KAAA,CACHG,CAAAA,GAAS,IAAA,CAClBH,CAAAA,CAAY,IAAA,CACHG,CAAAA,GAAS,GAAA,GAClBJ,CAAAA,CAAW,OAEb,QACF,CAEA,GAAII,CAAAA,GAAS,GAAA,CAAK,CAChBJ,CAAAA,CAAW,IAAA,CACXD,GAAUK,CAAAA,CACV,QACF,CAGA,GAAIA,CAAAA,GAAS,GAAA,EAAOD,CAAAA,CAAI,CAAA,CAAID,GAAOJ,CAAAA,CAAMK,CAAAA,CAAI,CAAC,CAAA,GAAM,GAAA,CAAK,CACvD,IAAME,CAAAA,CAAcP,EAAM,OAAA,CAAQ;AAAA,CAAA,CAAMK,CAAAA,CAAI,CAAC,CAAA,CAC7C,GAAIE,IAAgB,EAAA,CAClB,MAEFF,CAAAA,CAAIE,CAAAA,CACJN,CAAAA,EAAU;AAAA,CAAA,CACV,QACF,CAGA,GAAIK,CAAAA,GAAS,KAAOD,CAAAA,CAAI,CAAA,CAAID,CAAAA,EAAOJ,CAAAA,CAAMK,EAAI,CAAC,CAAA,GAAM,GAAA,CAAK,CACvD,IAAMG,CAAAA,CAAaR,CAAAA,CAAM,OAAA,CAAQ,IAAA,CAAMK,CAAAA,CAAI,CAAC,CAAA,CAC5C,GAAIG,IAAe,EAAA,CACjB,MAEFH,CAAAA,CAAIG,CAAAA,CAAa,EACjB,QACF,CAEAP,CAAAA,EAAUK,EACZ,CAEA,OAAOL,CACT,CAMA,SAASQ,CAAAA,CAAsBnB,CAAAA,CAAsB,CACnD,IAAIW,EAAS,EAAA,CACTS,CAAAA,CAAW,KAAA,CACXC,CAAAA,CAAW,MACXR,CAAAA,CAAY,KAAA,CAEhB,IAAA,IAASE,CAAAA,CAAI,EAAGA,CAAAA,CAAIf,CAAAA,CAAK,MAAA,CAAQe,CAAAA,EAAAA,CAAK,CACpC,IAAMC,CAAAA,CAAOhB,CAAAA,CAAKe,CAAC,CAAA,CACnB,GAAIF,CAAAA,CAAW,CACbF,GAAUK,CAAAA,CACVH,CAAAA,CAAY,KAAA,CACZ,QACF,CACA,GAAIG,CAAAA,GAAS,IAAA,CAAM,CACjBL,CAAAA,EAAUK,CAAAA,CACVH,CAAAA,CAAY,IAAA,CACZ,QACF,CACA,GAAIG,CAAAA,GAAS,GAAA,EAAO,CAACK,CAAAA,CAAU,CAC7BD,CAAAA,CAAW,CAACA,EACZT,CAAAA,EAAUK,CAAAA,CACV,QACF,CACA,GAAIA,CAAAA,GAAS,GAAA,EAAO,CAACI,EAAU,CAC7BC,CAAAA,CAAW,CAACA,CAAAA,CACZV,GAAU,GAAA,CACV,QACF,CACAA,CAAAA,EAAUK,EACZ,CACA,OAAOL,CACT,CAMO,SAASW,CAAAA,CAAWZ,CAAAA,CAAea,CAAAA,CAAiC,CACzE,IAAMtB,CAAAA,CAAgBsB,CAAAA,EAAS,aAAA,EAAiB,KAC5CC,CAAAA,CAAMzB,CAAAA,CAAqBW,CAAAA,CAAOT,CAAa,EAEnD,GAAI,CAACuB,CAAAA,CACH,OAAO,GAITA,CAAAA,CAAMf,CAAAA,CAAce,CAAG,CAAA,CAGvBA,EAAML,CAAAA,CAAsBK,CAAG,CAAA,CAG/BA,CAAAA,CAAMA,EAAI,OAAA,CAAQ,sBAAA,CAAwB,QAAQ,CAAA,CAGlDA,EAAMA,CAAAA,CAAI,OAAA,CAAQ,sBAAA,CAAwB,MAAM,CAAA,CAGhDA,CAAAA,CAAMA,CAAAA,CAAI,OAAA,CAAQ,UAAW,GAAG,CAAA,CAGhCA,CAAAA,CAAMA,CAAAA,CAAI,QAAQ,QAAA,CAAU,SAAS,CAAA,CAGrCA,CAAAA,CAAMA,EAAI,OAAA,CACR,mDAAA,CACA,CAACC,CAAAA,CAAIC,CAAAA,GAAS,CACZ,IAAMC,CAAAA,CAAQD,EAAK,WAAA,EAAY,CAC/B,OACEC,CAAAA,GAAU,QACVA,CAAAA,GAAU,OAAA,EACVA,CAAAA,GAAU,MAAA,EACVD,IAAS,MAAA,EACTA,CAAAA,GAAS,KAAA,EACTA,CAAAA,GAAS,UAAA,EACT,kBAAA,CAAmB,IAAA,CAAKA,CAAI,GAC5B,sBAAA,CAAuB,IAAA,CAAKA,CAAI,CAAA,EAChC,mBAAmB,IAAA,CAAKA,CAAI,CAAA,CAErB,CAAA,EAAA,EAAKA,CAAI,CAAA,CAAA,CAEX,CAAA,GAAA,EAAMA,CAAI,CAAA,CAAA,CACnB,CACF,CAAA,CAGAF,CAAAA,CAAMA,CAAAA,CAAI,OAAA,CAAQ,iDAAkD,SAAS,CAAA,CAG7EA,CAAAA,CAAMA,CAAAA,CAAI,QAAQ,gDAAA,CAAkD,WAAW,CAAA,CAG/EA,CAAAA,CAAMA,EAAI,OAAA,CAAQ,cAAA,CAAgB,MAAM,CAAA,CACxCA,CAAAA,CAAMA,CAAAA,CAAI,OAAA,CAAQ,kBAAA,CAAoB,QAAQ,CAAA,CAE9C,IAAMI,CAAAA,CAA0B,GAC5BhB,CAAAA,CAAW,KAAA,CACXC,CAAAA,CAAY,KAAA,CACZF,EAAS,EAAA,CACPG,CAAAA,CAAMU,CAAAA,CAAI,MAAA,CAEhB,IAAA,IAAST,CAAAA,CAAI,CAAA,CAAGA,CAAAA,CAAID,EAAKC,CAAAA,EAAAA,CAAK,CAC5B,IAAMC,CAAAA,CAAOQ,EAAIT,CAAC,CAAA,CAElB,GAAIH,CAAAA,CAAU,CACZ,GAAIC,CAAAA,CAAW,CACbF,CAAAA,EAAUK,CAAAA,CACVH,CAAAA,CAAY,KAAA,CACZ,QACF,CAEA,GAAIG,CAAAA,GAAS,IAAA,CAAM,CACjBH,EAAY,IAAA,CACZF,CAAAA,EAAUK,CAAAA,CACV,QACF,CAEA,GAAIA,CAAAA,GAAS,GAAA,CAAK,CAChBJ,EAAW,KAAA,CACXD,CAAAA,EAAUK,CAAAA,CACV,QACF,CAGA,IAAMa,CAAAA,CAAOb,CAAAA,CAAK,UAAA,CAAW,CAAC,CAAA,CAC9B,GAAIa,CAAAA,CAAO,EAAA,CAAI,CACTb,CAAAA,GAAS;AAAA,CAAA,CACXL,CAAAA,EAAU,KAAA,CACDK,CAAAA,GAAS,IAAA,CAClBL,CAAAA,EAAU,KAAA,CACDK,CAAAA,GAAS,GAAA,CAClBL,CAAAA,EAAU,KAAA,CACDK,CAAAA,GAAS,IAAA,CAClBL,CAAAA,EAAU,MACDK,CAAAA,GAAS,IAAA,CAClBL,CAAAA,EAAU,KAAA,CAEVA,CAAAA,EAAU,KAAA,CAAQkB,CAAAA,CAAK,QAAA,CAAS,EAAE,CAAA,CAAE,QAAA,CAAS,CAAA,CAAG,GAAG,CAAA,CAErD,QACF,CAEAlB,CAAAA,EAAUK,CAAAA,CACV,QACF,CAIA,GAAIA,CAAAA,GAAS,GAAA,CAAK,CAEhB,IAAMc,CAAAA,CAAenB,CAAAA,CAAO,OAAA,EAAQ,CAC9BoB,CAAAA,CAAWD,CAAAA,CAAaA,CAAAA,CAAa,MAAA,CAAS,CAAC,CAAA,CAAA,CAEnDC,CAAAA,GAAa,GAAA,EACbA,CAAAA,GAAa,GAAA,EACbA,CAAAA,GAAa,GAAA,EACbA,CAAAA,GAAa,GAAA,EACbA,CAAAA,GAAa,GAAA,IAEbpB,EAASmB,CAAAA,CAAe,IAAA,CAAA,CAG1BlB,CAAAA,CAAW,IAAA,CACXD,CAAAA,EAAUK,CAAAA,CACV,QACF,CAEA,GAAIA,CAAAA,GAAS,GAAA,EAAOA,CAAAA,GAAS,GAAA,CAAK,CAEhC,IAAMc,CAAAA,CAAenB,CAAAA,CAAO,OAAA,EAAQ,CAC9BoB,CAAAA,CAAWD,CAAAA,CAAaA,CAAAA,CAAa,MAAA,CAAS,CAAC,CAAA,CAAA,CACjDC,CAAAA,GAAa,GAAA,EAAOA,CAAAA,GAAa,GAAA,EAAOA,IAAa,GAAA,IACvDpB,CAAAA,CAASmB,CAAAA,CAAe,IAAA,CAAA,CAG1BF,CAAAA,CAAM,IAAA,CAAKZ,CAAI,CAAA,CACfL,CAAAA,EAAUK,CAAAA,CACV,QACF,CAEA,GAAIA,CAAAA,GAAS,GAAA,CAAK,CAChB,GAAIY,CAAAA,CAAM,MAAA,CAAS,CAAA,EAAKA,CAAAA,CAAMA,CAAAA,CAAM,MAAA,CAAS,CAAC,CAAA,GAAM,GAAA,GAClDA,CAAAA,CAAM,GAAA,EAAI,CACVjB,CAAAA,CAASA,EAAO,OAAA,CAAQ,OAAA,CAAS,EAAE,CAAA,CACnCA,CAAAA,EAAUK,CAAAA,CAENY,CAAAA,CAAM,MAAA,GAAW,CAAA,CAAA,CACnB,MAGJ,QACF,CAEA,GAAIZ,CAAAA,GAAS,IAAK,CAChB,GAAIY,CAAAA,CAAM,MAAA,CAAS,CAAA,EAAKA,CAAAA,CAAMA,CAAAA,CAAM,MAAA,CAAS,CAAC,CAAA,GAAM,GAAA,GAClDA,CAAAA,CAAM,GAAA,EAAI,CACVjB,EAASA,CAAAA,CAAO,OAAA,CAAQ,OAAA,CAAS,EAAE,CAAA,CACnCA,CAAAA,EAAUK,CAAAA,CAENY,CAAAA,CAAM,MAAA,GAAW,CAAA,CAAA,CACnB,MAGJ,QACF,CAEAjB,CAAAA,EAAUK,EACZ,CAmEA,GA9DIJ,CAAAA,GACEC,CAAAA,EAEEF,CAAAA,CAAO,QAAA,CAAS,IAAI,CAAA,GACtBA,CAAAA,CAASA,CAAAA,CAAO,KAAA,CAAM,CAAA,CAAG,EAAE,CAAA,CAAA,CAI/BA,CAAAA,CAASA,EAAO,OAAA,CAAQ,sBAAA,CAAwB,EAAE,CAAA,CAGlDA,CAAAA,EAAU,GAAA,CAAA,CAIZA,CAAAA,CAASA,CAAAA,CAAO,OAAA,EAAQ,CAGxBA,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQ,qBAAA,CAAuB,QAAQ,CAAA,CACvDA,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQ,sBAAA,CAAwB,SAAS,CAAA,CACzDA,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQ,qBAAA,CAAuB,QAAQ,CAAA,CACvDA,CAAAA,CAASA,EAAO,OAAA,CAAQ,oBAAA,CAAsB,QAAQ,CAAA,CACtDA,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQ,yBAAA,CAA2B,QAAQ,CAAA,CAC3DA,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQ,0BAAA,CAA4B,QAAQ,CAAA,CAI5DA,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQ,qBAAA,CAAuB,QAAQ,CAAA,CACvDA,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQ,yBAAA,CAA2B,SAAS,CAAA,CAC5DA,CAAAA,CAASA,CAAAA,CAAO,QAAQ,qBAAA,CAAuB,QAAQ,CAAA,CAGvDA,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQ,0BAAA,CAA4B,SAAS,CAAA,CAC7DA,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQ,8BAAA,CAAgC,UAAU,EAClEA,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQ,0BAAA,CAA4B,SAAS,CAAA,CAI7DA,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQ,iCAAA,CAAmC,QAAQ,CAAA,CACnEA,CAAAA,CAASA,CAAAA,CAAO,QAAQ,sCAAA,CAAwC,SAAS,CAAA,CAGzEA,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQ,8CAAA,CAAgD,MAAM,CAAA,CAC9EA,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQ,mDAAA,CAAqD,OAAO,CAAA,CAGpFA,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQ,yBAAA,CAA2B,KAAK,CAAA,CACxDA,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQ,8BAAA,CAAgC,MAAM,CAAA,CAI1D,OAAA,CAAQ,IAAA,CAAKA,CAAM,IACrBA,CAAAA,EAAU,MAAA,CAAA,CAIZA,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQ,OAAA,CAAS,EAAE,CAAA,CAGnCA,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQ,6CAAA,CAA+C,SAAS,CAAA,CAG5EiB,EAAM,MAAA,CAAS,CAAA,EAAKA,CAAAA,CAAMA,CAAAA,CAAM,MAAA,CAAS,CAAC,CAAA,GAAM,GAAA,CAAK,CACvD,IAAMI,CAAAA,CACJrB,CAAAA,CAAO,KAAA,CAAM,wBAAwB,GACrCA,CAAAA,CAAO,KAAA,CAAM,yBAAyB,CAAA,CACxC,GAAIqB,CAAAA,CAAkB,CACpB,IAAMC,CAAAA,CAAWD,CAAAA,CAAiB,CAAC,CAAA,CAC/BC,CAAAA,CAAS,UAAA,CAAW,GAAG,CAAA,CACzBtB,CAAAA,CAASA,CAAAA,CAAO,KAAA,CAAM,CAAA,CAAGA,CAAAA,CAAO,MAAA,CAASsB,CAAAA,CAAS,MAAA,CAAS,CAAC,CAAA,CAE5DtB,CAAAA,CAASA,CAAAA,CAAO,KAAA,CAAM,CAAA,CAAGA,EAAO,MAAA,CAASsB,CAAAA,CAAS,MAAM,EAE5D,CACF,CAMA,IAHAtB,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQ,OAAA,CAAS,EAAE,CAAA,CAAE,OAAA,GAG9BiB,CAAAA,CAAM,MAAA,CAAS,CAAA,EAAG,CACvB,IAAMM,CAAAA,CAAWN,CAAAA,CAAM,GAAA,EAAI,CAC3BjB,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQ,OAAA,CAAS,EAAE,EAAE,OAAA,EAAQ,CACzCuB,CAAAA,GAAa,GAAA,CACfvB,CAAAA,EAAU,GAAA,CACDuB,CAAAA,GAAa,GAAA,GACtBvB,CAAAA,EAAU,GAAA,EAEd,CAEA,OAAOA,CACT,CAYO,SAASwB,CAAAA,CACdzB,CAAAA,CACAa,CAAAA,CACmB,CACnB,IAAMa,CAAAA,CAAYb,CAAAA,EAAS,QAAA,EAAY,IAAA,CAEvC,GAAI,CAACb,CAAAA,EAAS,OAAOA,CAAAA,EAAU,QAAA,CAC7B,OAAO0B,CAAAA,CAGT,IAAMC,CAAAA,CAAWf,CAAAA,CAAWZ,CAAAA,CAAO,CACjC,aAAA,CAAea,CAAAA,EAAS,aAAA,EAAiB,IAC3C,CAAC,CAAA,CAED,GAAI,CAACc,CAAAA,CACH,OAAOD,CAAAA,CAGT,GAAI,CACF,OAAO,IAAA,CAAK,KAAA,CAAMC,CAAQ,CAC5B,CAAA,MAASC,CAAAA,CAAO,CACd,GAAIf,CAAAA,EAAS,MAAA,CACX,MAAMe,CAAAA,CAER,OAAOF,CACT,CACF,CCvdO,IAAMG,CAAAA,CAAN,KAAgC,CAC7B,MAAA,CAAS,EAAA,CACT,UAAA,CAAgC,IAAA,CAChC,WAAA,CAAc,KAAA,CACd,OAAA,CAER,WAAA,CAAYhB,CAAAA,CAA+B,EAAC,CAAG,CAC7C,IAAA,CAAK,OAAA,CAAUA,CAAAA,CACf,IAAA,CAAK,UAAA,CAAcA,CAAAA,CAAQ,QAAA,EAAY,KACzC,CAKO,KAAKiB,CAAAA,CAAkC,CAC5C,GAAI,IAAA,CAAK,WAAA,CACP,OAAO,IAAA,CAAK,UAAA,CAGd,IAAA,CAAK,MAAA,EAAUA,CAAAA,CACf,IAAMC,CAAAA,CAASN,CAAAA,CAAmB,KAAK,MAAA,CAAQ,IAAA,CAAK,OAAO,CAAA,CAE3D,OAAIM,CAAAA,GAAW,IAAA,GACb,IAAA,CAAK,UAAA,CAAaA,CAAAA,CAClB,IAAA,CAAK,OAAA,CAAQ,QAAA,GAAWA,CAAM,GAGzB,IAAA,CAAK,UACd,CAKO,MAAA,EAA4B,CACjC,OAAA,IAAA,CAAK,WAAA,CAAc,IAAA,CACZ,IAAA,CAAK,UACd,CAKO,YAAA,EAAuB,CAC5B,OAAO,IAAA,CAAK,MACd,CAKO,SAAA,EAA+B,CACpC,OAAO,IAAA,CAAK,UACd,CAKO,KAAA,EAAc,CACnB,IAAA,CAAK,MAAA,CAAS,EAAA,CACd,IAAA,CAAK,UAAA,CAAc,KAAK,OAAA,CAAQ,QAAA,EAAY,IAAA,CAC5C,IAAA,CAAK,WAAA,CAAc,MACrB,CACF,CAAA,CCcO,SAASC,CAAAA,CACdnB,CAAAA,CAAsC,EAAC,CACZ,CAC3B,GAAM,CACJ,UAAA,CAAAoB,CAAAA,CAAa,EAAA,CACb,WAAA,CAAAC,CAAAA,CAAc,IAAA,CACd,UAAA,CAAAC,CAAAA,CACA,QAAA,CAAAC,CAAAA,CACA,OAAA,CAAAC,CAAAA,CACA,GAAGC,CACL,CAAA,CAAIzB,CAAAA,CAEE,CAAC0B,CAAAA,CAAMC,CAAO,CAAA,CAAIC,QAAAA,CAA4BP,CAAW,CAAA,CACzD,CAACQ,CAAAA,CAAWC,CAAY,CAAA,CAAIF,QAAAA,CAAS,KAAK,CAAA,CAC1C,CAACG,CAAAA,CAAYC,CAAa,CAAA,CAAIJ,QAAAA,CAAS,KAAK,CAAA,CAC5C,CAACb,CAAAA,CAAOkB,CAAQ,CAAA,CAAIL,QAAAA,CAAuB,IAAI,CAAA,CAC/C,CAACM,CAAAA,CAASC,CAAU,CAAA,CAAIP,QAAAA,CAAS,EAAE,CAAA,CAEnCQ,CAAAA,CAAYC,MAAAA,CAA+B,IAAI,CAAA,CAC/CC,CAAAA,CAAoBD,MAAAA,CAAe,CAAC,CAAA,CACpCE,EAAmBF,MAAAA,CAA6C,IAAI,CAAA,CACpEG,CAAAA,CAAgBH,MAAAA,CAA0BhB,CAAW,CAAA,CAGtDe,CAAAA,CAAU,OAAA,GACbA,CAAAA,CAAU,OAAA,CAAU,IAAIpB,CAAAA,CAAgB,CACtC,GAAGS,CAAAA,CACH,QAAA,CAAWgB,CAAAA,EAAY,CACrBlB,CAAAA,GAAWkB,CAAO,EACpB,CAAA,CACA,OAAA,CAAUC,CAAAA,EAAQ,CAChBT,CAAAA,CAASS,CAAG,CAAA,CACZlB,CAAAA,GAAUkB,CAAG,EACf,CACF,CAAC,CAAA,CAAA,CAIHC,SAAAA,CAAU,IACD,IAAM,CACPJ,CAAAA,CAAiB,OAAA,EACnB,YAAA,CAAaA,CAAAA,CAAiB,OAAO,EAEzC,EACC,EAAE,CAAA,CAEL,IAAMK,CAAAA,CAAiBC,WAAAA,CACrB,CAACC,CAAAA,CAA6B7C,CAAAA,GAAgB,CAI5C,GAHAuC,CAAAA,CAAc,OAAA,CAAUM,CAAAA,CACxBX,EAAWlC,CAAG,CAAA,CAEVmB,CAAAA,EAAc,CAAA,CAAG,CACnBO,CAAAA,CAAQmB,CAAQ,CAAA,CAChB,MACF,CAEA,IAAMC,CAAAA,CAAM,WAAA,CAAY,GAAA,GAClBC,CAAAA,CAAgBD,CAAAA,CAAMT,CAAAA,CAAkB,OAAA,CAE1CU,CAAAA,EAAiB5B,CAAAA,EACnBkB,CAAAA,CAAkB,OAAA,CAAUS,CAAAA,CAC5BpB,CAAAA,CAAQmB,CAAQ,CAAA,EACNP,CAAAA,CAAiB,OAAA,GAC3BA,CAAAA,CAAiB,OAAA,CAAU,UAAA,CAAW,IAAM,CAC1CD,CAAAA,CAAkB,OAAA,CAAU,WAAA,CAAY,GAAA,EAAI,CAC5CX,CAAAA,CAAQa,CAAAA,CAAc,OAAO,CAAA,CAC7BD,CAAAA,CAAiB,OAAA,CAAU,KAC7B,CAAA,CAAGnB,CAAAA,CAAa4B,CAAa,CAAA,EAEjC,CAAA,CACA,CAAC5B,CAAU,CACb,CAAA,CAEM6B,CAAAA,CAAOJ,WAAAA,CACV5B,CAAAA,EAAkB,CACjB,GAAI,CAACmB,CAAAA,CAAU,OAAA,CAAS,OACxBN,CAAAA,CAAa,IAAI,CAAA,CACjB,IAAMhB,CAAAA,CAAWsB,CAAAA,CAAU,OAAA,CAAQ,IAAA,CAAKnB,CAAK,CAAA,CAC7C2B,CAAAA,CAAe9B,EAAUsB,CAAAA,CAAU,OAAA,CAAQ,YAAA,EAAc,EAC3D,CAAA,CACA,CAACQ,CAAc,CACjB,CAAA,CAEMM,CAAAA,CAASL,WAAAA,CAAY,IAAM,CAC/B,GAAI,CAACT,CAAAA,CAAU,OAAA,CAAS,OACpBG,CAAAA,CAAiB,OAAA,GACnB,YAAA,CAAaA,CAAAA,CAAiB,OAAO,CAAA,CACrCA,CAAAA,CAAiB,OAAA,CAAU,IAAA,CAAA,CAG7B,IAAMY,CAAAA,CAAcf,EAAU,OAAA,CAAQ,MAAA,EAAO,CAC7CT,CAAAA,CAAQwB,CAAW,CAAA,CACnBhB,CAAAA,CAAWC,CAAAA,CAAU,OAAA,CAAQ,YAAA,EAAc,CAAA,CAC3CN,CAAAA,CAAa,KAAK,EAClBE,CAAAA,CAAc,IAAI,CAAA,CAClBV,CAAAA,GAAa6B,CAAW,EAC1B,CAAA,CAAG,CAAC7B,CAAU,CAAC,CAAA,CAET8B,CAAAA,CAAQP,WAAAA,CAAY,IAAM,CAC1BN,CAAAA,CAAiB,OAAA,GACnB,YAAA,CAAaA,CAAAA,CAAiB,OAAO,CAAA,CACrCA,CAAAA,CAAiB,OAAA,CAAU,IAAA,CAAA,CAE7BH,CAAAA,CAAU,OAAA,EAAS,KAAA,EAAM,CACzBT,CAAAA,CAAQN,CAAW,CAAA,CACnBc,CAAAA,CAAW,EAAE,CAAA,CACbL,CAAAA,CAAa,KAAK,CAAA,CAClBE,CAAAA,CAAc,KAAK,CAAA,CACnBC,CAAAA,CAAS,IAAI,EACf,CAAA,CAAG,CAACZ,CAAW,CAAC,CAAA,CAEVgC,CAAAA,CAAOR,WAAAA,CACX,MAAOS,CAAAA,EAAwE,CAC7EF,CAAAA,EAAM,CACNtB,CAAAA,CAAa,IAAI,CAAA,CAEjB,GAAI,CACF,GAAI,WAAA,GAAewB,CAAAA,CAAQ,CAEzB,IAAMC,CAAAA,CAAUD,CAAAA,CAA0B,SAAA,EAAU,CAC9CE,CAAAA,CAAU,IAAI,WAAA,CAEpB,OAAa,CACX,GAAM,CAAE,IAAA,CAAAC,CAAAA,CAAM,KAAA,CAAAC,CAAM,CAAA,CAAI,MAAMH,CAAAA,CAAO,IAAA,EAAK,CAC1C,GAAIE,CAAAA,CAAM,MAEV,IAAME,CAAAA,CAAY,OAAOD,CAAAA,EAAU,QAAA,CAAWA,CAAAA,CAAQF,CAAAA,CAAQ,MAAA,CAAOE,CAAAA,CAAO,CAAE,MAAA,CAAQ,CAAA,CAAK,CAAC,CAAA,CAC5FT,CAAAA,CAAKU,CAAS,EAChB,CACF,MAEE,UAAA,IAAiB1C,CAAAA,IAASqC,CAAAA,CACxBL,CAAAA,CAAKhC,CAAK,EAGhB,CAAA,MAASyB,CAAAA,CAAK,CACZ,IAAMkB,CAAAA,CAAWlB,CAAAA,YAAe,KAAA,CAAQA,CAAAA,CAAM,IAAI,KAAA,CAAM,MAAA,CAAOA,CAAG,CAAC,CAAA,CACnET,CAAAA,CAAS2B,CAAQ,CAAA,CACjBpC,CAAAA,GAAUoC,CAAQ,EACpB,CAAA,OAAE,CACAV,CAAAA,GACF,CACF,CAAA,CACA,CAACD,CAAAA,CAAMC,CAAAA,CAAQE,CAAAA,CAAO5B,CAAO,CAC/B,CAAA,CAEA,OAAO,CACL,IAAA,CAAAE,CAAAA,CACA,SAAA,CAAAG,CAAAA,CACA,UAAA,CAAAE,CAAAA,CACA,KAAA,CAAAhB,CAAAA,CACA,OAAA,CAAAmB,CAAAA,CACA,IAAA,CAAAe,CAAAA,CACA,IAAA,CAAAI,CAAAA,CACA,MAAA,CAAAH,CAAAA,CACA,KAAA,CAAAE,CACF,CACF","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,74 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stream-heal",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.1",
|
|
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
|
+
}
|