unclosed 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/LICENSE +21 -0
- package/README.md +136 -0
- package/dist/cjs/index.js +20 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/parse.js +377 -0
- package/dist/cjs/parse.js.map +1 -0
- package/dist/cjs/stream.js +98 -0
- package/dist/cjs/stream.js.map +1 -0
- package/dist/esm/index.js +8 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/parse.js +367 -0
- package/dist/esm/parse.js.map +1 -0
- package/dist/esm/stream.js +93 -0
- package/dist/esm/stream.js.map +1 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/parse.d.ts +77 -0
- package/dist/types/stream.d.ts +54 -0
- package/package.json +58 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tolerant JSON parser for incomplete ("unclosed") input.
|
|
3
|
+
*
|
|
4
|
+
* The parser reads as much of a JSON document as is present and closes any
|
|
5
|
+
* containers that were still open when the input ended. It never guesses at
|
|
6
|
+
* data it has not seen: a truncated object key, a half-written escape sequence
|
|
7
|
+
* or an unrecoverable number is dropped rather than invented.
|
|
8
|
+
*
|
|
9
|
+
* Zero dependencies. No configuration files. No runtime requirements beyond
|
|
10
|
+
* ES2020.
|
|
11
|
+
*/
|
|
12
|
+
export type Json = null | boolean | number | string | Json[] | {
|
|
13
|
+
[key: string]: Json;
|
|
14
|
+
};
|
|
15
|
+
export interface ParseOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Keep the received prefix of a string that has not been closed yet.
|
|
18
|
+
* Disable to drop unterminated strings entirely. Default: `true`.
|
|
19
|
+
*/
|
|
20
|
+
partialStrings?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Resolve truncated literals (`tru` -> `true`, `nul` -> `null`).
|
|
23
|
+
* Default: `true`.
|
|
24
|
+
*/
|
|
25
|
+
partialLiterals?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Skip Markdown code fences and any prose before the first `{` or `[`.
|
|
28
|
+
* Default: `true`.
|
|
29
|
+
*/
|
|
30
|
+
scan?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface ParseResult<T = Json> {
|
|
33
|
+
/** Best-effort value, or `undefined` when nothing could be recovered. */
|
|
34
|
+
value: T | undefined;
|
|
35
|
+
/** `true` when the input contained a whole, untruncated JSON value. */
|
|
36
|
+
complete: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Path to the node that was still being written when the input ended,
|
|
39
|
+
* or `null` when the value is complete. Useful for skipping fields that
|
|
40
|
+
* are not final yet.
|
|
41
|
+
*/
|
|
42
|
+
incompletePath: Array<string | number> | null;
|
|
43
|
+
}
|
|
44
|
+
/** Thrown for input that is malformed rather than merely unfinished. */
|
|
45
|
+
export declare class UnclosedError extends SyntaxError {
|
|
46
|
+
readonly position: number;
|
|
47
|
+
constructor(message: string, position: number);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Strip Markdown fences and leading prose so that model output such as
|
|
51
|
+
* "Sure! ```json\n{..." parses without pre-processing on the caller's side.
|
|
52
|
+
*/
|
|
53
|
+
export declare function scanToJson(input: string): string;
|
|
54
|
+
/**
|
|
55
|
+
* Parse possibly-incomplete JSON and report how much of it is final.
|
|
56
|
+
*
|
|
57
|
+
* @throws {UnclosedError} when the input is malformed rather than unfinished.
|
|
58
|
+
*/
|
|
59
|
+
export declare function parseWithMeta<T = Json>(input: string, options?: ParseOptions): ParseResult<T>;
|
|
60
|
+
/**
|
|
61
|
+
* Parse possibly-incomplete JSON.
|
|
62
|
+
*
|
|
63
|
+
* Returns `undefined` when no value could be recovered at all.
|
|
64
|
+
*
|
|
65
|
+
* @throws {UnclosedError} when the input is malformed rather than unfinished.
|
|
66
|
+
*/
|
|
67
|
+
export declare function parse<T = Json>(input: string, options?: ParseOptions): T | undefined;
|
|
68
|
+
/** Parse without throwing. Returns `undefined` on malformed input. */
|
|
69
|
+
export declare function tryParse<T = Json>(input: string, options?: ParseOptions): T | undefined;
|
|
70
|
+
/** `true` when the input holds one whole, untruncated JSON value. */
|
|
71
|
+
export declare function isComplete(input: string, options?: ParseOptions): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Turn possibly-incomplete JSON into a valid JSON string.
|
|
74
|
+
*
|
|
75
|
+
* @throws {UnclosedError} when the input is malformed rather than unfinished.
|
|
76
|
+
*/
|
|
77
|
+
export declare function repair(input: string, options?: ParseOptions): string;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Incremental helpers for feeding model output in as it arrives.
|
|
3
|
+
*/
|
|
4
|
+
import type { Json, ParseOptions, ParseResult } from "./parse.js";
|
|
5
|
+
export interface StreamSnapshot<T = Json> extends ParseResult<T> {
|
|
6
|
+
/** Raw text received so far. */
|
|
7
|
+
text: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Accumulates chunks and exposes the best-effort value after each one.
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* const acc = new PartialJson()
|
|
14
|
+
* for await (const chunk of tokens) {
|
|
15
|
+
* const snapshot = acc.push(chunk)
|
|
16
|
+
* render(snapshot.value)
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare class PartialJson<T = Json> {
|
|
21
|
+
private buffer;
|
|
22
|
+
private snapshot;
|
|
23
|
+
private readonly options;
|
|
24
|
+
constructor(options?: ParseOptions);
|
|
25
|
+
/** Append a chunk and return the current snapshot. */
|
|
26
|
+
push(chunk: string): StreamSnapshot<T>;
|
|
27
|
+
/** Latest best-effort value. */
|
|
28
|
+
get value(): T | undefined;
|
|
29
|
+
/** `true` once a whole JSON value has been received. */
|
|
30
|
+
get complete(): boolean;
|
|
31
|
+
/** Path of the node still being written, or `null`. */
|
|
32
|
+
get incompletePath(): Array<string | number> | null;
|
|
33
|
+
/** Everything received so far. */
|
|
34
|
+
get text(): string;
|
|
35
|
+
/** Clear all state so the instance can be reused. */
|
|
36
|
+
reset(): void;
|
|
37
|
+
}
|
|
38
|
+
export interface StreamOptions extends ParseOptions {
|
|
39
|
+
/**
|
|
40
|
+
* Only yield when the parsed value actually changed, instead of once per
|
|
41
|
+
* chunk. Default: `true`.
|
|
42
|
+
*/
|
|
43
|
+
onlyOnChange?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Turn a stream of text chunks into a stream of progressively complete values.
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* for await (const { value, complete } of parseStream(tokens)) {
|
|
50
|
+
* render(value)
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare function parseStream<T = Json>(source: AsyncIterable<string> | Iterable<string>, options?: StreamOptions): AsyncGenerator<StreamSnapshot<T>, void, void>;
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "unclosed",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Parse JSON that hasn't finished arriving yet. Zero dependencies, built for streaming LLM output.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"json",
|
|
7
|
+
"partial",
|
|
8
|
+
"incomplete",
|
|
9
|
+
"streaming",
|
|
10
|
+
"parser",
|
|
11
|
+
"llm",
|
|
12
|
+
"openai",
|
|
13
|
+
"anthropic",
|
|
14
|
+
"tolerant",
|
|
15
|
+
"repair",
|
|
16
|
+
"zero-dependency"
|
|
17
|
+
],
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"type": "commonjs",
|
|
20
|
+
"main": "./dist/cjs/index.js",
|
|
21
|
+
"module": "./dist/esm/index.js",
|
|
22
|
+
"types": "./dist/types/index.d.ts",
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/types/index.d.ts",
|
|
27
|
+
"import": "./dist/esm/index.js",
|
|
28
|
+
"require": "./dist/cjs/index.js",
|
|
29
|
+
"default": "./dist/cjs/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./package.json": "./package.json"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE",
|
|
37
|
+
"CHANGELOG.md"
|
|
38
|
+
],
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=16"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "bash scripts/build.sh",
|
|
44
|
+
"test": "npm run build && node --test \"test/*.test.mjs\"",
|
|
45
|
+
"prepublishOnly": "npm run build"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"typescript": "^5.4.0"
|
|
49
|
+
},
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/OWNER/unclosed.git"
|
|
53
|
+
},
|
|
54
|
+
"bugs": {
|
|
55
|
+
"url": "https://github.com/OWNER/unclosed/issues"
|
|
56
|
+
},
|
|
57
|
+
"homepage": "https://github.com/OWNER/unclosed#readme"
|
|
58
|
+
}
|