links-notation 0.23.0 → 0.25.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/README.md +86 -0
- package/README.ru.md +48 -0
- package/dist/index.js +597 -269
- package/index.d.ts +58 -1
- package/package.json +1 -1
- package/src/Binary.js +2 -20
- package/src/IndentedDocument.js +88 -0
- package/src/Link.js +9 -51
- package/src/Parser.js +26 -1
- package/src/ReferenceLiteral.js +66 -0
- package/src/grammar.pegjs +12 -2
- package/src/index.js +9 -0
- package/src/parser-generated.js +262 -89
- package/src/references.js +63 -0
- package/tests/Binary.test.js +3 -1
- package/tests/EmptyReference.test.js +2 -2
- package/tests/IndentedDocument.test.js +256 -0
- package/tests/ReferenceFidelity.test.js +99 -0
package/index.d.ts
CHANGED
|
@@ -100,9 +100,29 @@ export class Link {
|
|
|
100
100
|
* @param reference - The reference to escape
|
|
101
101
|
* @returns Escaped reference
|
|
102
102
|
*/
|
|
103
|
-
static escapeReference(
|
|
103
|
+
static escapeReference(
|
|
104
|
+
reference: string,
|
|
105
|
+
options?: EscapeReferenceOptions
|
|
106
|
+
): string;
|
|
107
|
+
|
|
108
|
+
/** Decode one escaped reference. Link IDs returned by a parser are already decoded. */
|
|
109
|
+
static unescapeReference(reference: string): string;
|
|
104
110
|
}
|
|
105
111
|
|
|
112
|
+
export interface EscapeReferenceOptions {
|
|
113
|
+
/** Leave ordinary single-space prose unquoted for use on document lines. */
|
|
114
|
+
minimal?: boolean;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Escape a reference. Minimal mode doubles enclosing quotes; default formatting uses n-quotes. */
|
|
118
|
+
export function escapeReference(
|
|
119
|
+
reference: string,
|
|
120
|
+
options?: EscapeReferenceOptions
|
|
121
|
+
): string;
|
|
122
|
+
|
|
123
|
+
/** Decode a complete escaped reference, including n-quotes and versioned literals. */
|
|
124
|
+
export function unescapeReference(reference: string): string;
|
|
125
|
+
|
|
106
126
|
/**
|
|
107
127
|
* Parser options for configuring the parser behavior
|
|
108
128
|
*/
|
|
@@ -166,8 +186,40 @@ export class Parser {
|
|
|
166
186
|
* @throws {ParseError} If the text does not parse
|
|
167
187
|
*/
|
|
168
188
|
parse(input: string): Link[];
|
|
189
|
+
|
|
190
|
+
/** Read the indentation tree without expanding it into path combinations. */
|
|
191
|
+
parseGroups(input: string): IndentedGroup[];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** A parsed line and the immediately indented lines below it. */
|
|
195
|
+
export interface IndentedGroup extends LinksGroup {
|
|
196
|
+
element: Link;
|
|
197
|
+
children: IndentedGroup[];
|
|
169
198
|
}
|
|
170
199
|
|
|
200
|
+
export interface IndentedDocumentOptions extends ParserOptions {
|
|
201
|
+
/** Multiple children become an array (default) or text joined with newlines. */
|
|
202
|
+
multipleValues?: 'array' | 'join';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Read parent/children text. Repeated parents accumulate children in order.
|
|
207
|
+
* No children yields [], one yields a string, multiple yield an array by default.
|
|
208
|
+
* Named links with values and grandchildren are rejected.
|
|
209
|
+
*/
|
|
210
|
+
export function parseIndentedDocument(
|
|
211
|
+
input: string,
|
|
212
|
+
options?: IndentedDocumentOptions
|
|
213
|
+
): Map<string, string | string[]>;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Write minimally quoted text with two-space indentation and a final newline.
|
|
217
|
+
* Multiline strings remain one encoded reference; array entries become children.
|
|
218
|
+
*/
|
|
219
|
+
export function formatIndentedDocument(
|
|
220
|
+
entries: ReadonlyMap<string, string | string[]>
|
|
221
|
+
): string;
|
|
222
|
+
|
|
171
223
|
/** Options for incremental parsing. */
|
|
172
224
|
export interface StreamParserOptions extends ParserOptions {
|
|
173
225
|
/** Canonical parser instance to use for completed records. */
|
|
@@ -561,3 +613,8 @@ export class BinaryLinoCodec {
|
|
|
561
613
|
}
|
|
562
614
|
export function formatBinaryDocument(document: Link[]): string;
|
|
563
615
|
export function formatBinaryReference(reference: string): string;
|
|
616
|
+
|
|
617
|
+
/** Encode exact Unicode as a version 1 UTF-8 hex literal (~1{...}). Throws on unpaired surrogates. */
|
|
618
|
+
export function encodeReferenceLiteral(text: string): string;
|
|
619
|
+
/** Decode a complete literal. Throws on malformed hex, UTF-8, or unsupported versions. */
|
|
620
|
+
export function decodeReferenceLiteral(literal: string): string;
|
package/package.json
CHANGED
package/src/Binary.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Binary links notation v1. Packet and document layers share no transport. */
|
|
2
2
|
import { Link } from './Link.js';
|
|
3
|
+
import { formatReference } from './ReferenceLiteral.js';
|
|
3
4
|
import { Parser } from './Parser.js';
|
|
4
5
|
|
|
5
6
|
const U64 = (1n << 64n) - 1n;
|
|
@@ -695,26 +696,7 @@ function canonical(node) {
|
|
|
695
696
|
return new Link(node.id, node.values.map(canonical));
|
|
696
697
|
}
|
|
697
698
|
export function formatBinaryReference(text) {
|
|
698
|
-
|
|
699
|
-
text &&
|
|
700
|
-
!text.startsWith('#') &&
|
|
701
|
-
!/[\p{White_Space}\u001c-\u001f\ufeff():"'`]/u.test(text)
|
|
702
|
-
)
|
|
703
|
-
return text;
|
|
704
|
-
let choice = null;
|
|
705
|
-
for (const quote of ["'", '"', '`'])
|
|
706
|
-
if (!text.startsWith(quote)) {
|
|
707
|
-
let longest = 0,
|
|
708
|
-
run = 0;
|
|
709
|
-
for (const c of text) {
|
|
710
|
-
run = c === quote ? run + 1 : 0;
|
|
711
|
-
longest = Math.max(longest, run);
|
|
712
|
-
}
|
|
713
|
-
const count = (longest + 1) | 1;
|
|
714
|
-
if (choice === null || count < choice.count) choice = { quote, count };
|
|
715
|
-
}
|
|
716
|
-
const delimiter = choice.quote.repeat(choice.count);
|
|
717
|
-
return delimiter + text + delimiter;
|
|
699
|
+
return formatReference(text);
|
|
718
700
|
}
|
|
719
701
|
export function formatBinaryDocument(document) {
|
|
720
702
|
function nested(node, top = false) {
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Parser } from './Parser.js';
|
|
2
|
+
import { escapeReference } from './references.js';
|
|
3
|
+
|
|
4
|
+
// A text line can be a single quoted reference or a flat list of words. Named
|
|
5
|
+
// links and nested values have more structure than this document format holds.
|
|
6
|
+
function lineText(link) {
|
|
7
|
+
if (link.id !== null && link.values.length === 0) return link.id;
|
|
8
|
+
if (
|
|
9
|
+
link.id === null &&
|
|
10
|
+
link.values.length > 0 &&
|
|
11
|
+
link.values.every((value) => value.id !== null && value.values.length === 0)
|
|
12
|
+
) {
|
|
13
|
+
return link.values.map((value) => value.id).join(' ');
|
|
14
|
+
}
|
|
15
|
+
throw new TypeError(
|
|
16
|
+
'Indented document lines must contain only text references'
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Read text parents and their immediate text children into a Map. Repeated
|
|
22
|
+
* parents accumulate children in document order. Zero children yields [], one
|
|
23
|
+
* yields a string, and multiple yield an array (or joined text when requested).
|
|
24
|
+
* @param {string} input
|
|
25
|
+
* @param {Object} options - Parser options plus multipleValues: 'array' | 'join'
|
|
26
|
+
* @returns {Map<string, string|string[]>}
|
|
27
|
+
*/
|
|
28
|
+
export function parseIndentedDocument(input, options = {}) {
|
|
29
|
+
const { multipleValues = 'array' } = options;
|
|
30
|
+
if (multipleValues !== 'array' && multipleValues !== 'join') {
|
|
31
|
+
throw new TypeError('multipleValues must be "array" or "join"');
|
|
32
|
+
}
|
|
33
|
+
const groups = new Parser(options).parseGroups(input);
|
|
34
|
+
const collected = new Map();
|
|
35
|
+
for (const group of groups) {
|
|
36
|
+
const parent = lineText(group.element);
|
|
37
|
+
const values = collected.get(parent) ?? [];
|
|
38
|
+
for (const child of group.children) {
|
|
39
|
+
if (child.children.length > 0) {
|
|
40
|
+
throw new TypeError(
|
|
41
|
+
'Indented documents support only immediate children'
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
values.push(lineText(child.element));
|
|
45
|
+
}
|
|
46
|
+
collected.set(parent, values);
|
|
47
|
+
}
|
|
48
|
+
return new Map(
|
|
49
|
+
[...collected].map(([parent, values]) => [
|
|
50
|
+
parent,
|
|
51
|
+
values.length === 1
|
|
52
|
+
? values[0]
|
|
53
|
+
: values.length > 1 && multipleValues === 'join'
|
|
54
|
+
? values.join('\n')
|
|
55
|
+
: values,
|
|
56
|
+
])
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Write a text Map using minimal quoting and two-space child indentation.
|
|
62
|
+
* Multiline strings are encoded as one reference; array entries are separate
|
|
63
|
+
* children. Nonempty output ends with a newline.
|
|
64
|
+
* @param {Map<string, string|string[]>} entries
|
|
65
|
+
* @returns {string}
|
|
66
|
+
*/
|
|
67
|
+
export function formatIndentedDocument(entries) {
|
|
68
|
+
if (!(entries instanceof Map)) {
|
|
69
|
+
throw new TypeError('Indented document entries must be a Map');
|
|
70
|
+
}
|
|
71
|
+
const lines = [];
|
|
72
|
+
for (const [parent, value] of entries) {
|
|
73
|
+
if (typeof parent !== 'string') {
|
|
74
|
+
throw new TypeError('Indented document parents must be strings');
|
|
75
|
+
}
|
|
76
|
+
const values = Array.isArray(value) ? value : [value];
|
|
77
|
+
for (const child of values) {
|
|
78
|
+
if (typeof child !== 'string') {
|
|
79
|
+
throw new TypeError('Indented document children must be strings');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
lines.push(escapeReference(parent, { minimal: true }));
|
|
83
|
+
for (const child of values) {
|
|
84
|
+
lines.push(' ' + escapeReference(child, { minimal: true }));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return lines.length > 0 ? lines.join('\n') + '\n' : '';
|
|
88
|
+
}
|
package/src/Link.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { escapeReference, unescapeReference } from './references.js';
|
|
2
|
+
|
|
1
3
|
export class Link {
|
|
2
4
|
/**
|
|
3
5
|
* Create a new Link
|
|
@@ -79,60 +81,16 @@ export class Link {
|
|
|
79
81
|
/**
|
|
80
82
|
* Escape a reference string by adding quotes if necessary
|
|
81
83
|
* @param {string} reference - The reference to escape
|
|
84
|
+
* @param {{minimal?: boolean}} options - Allow plain-text document lines
|
|
82
85
|
* @returns {string} Escaped reference
|
|
83
86
|
*/
|
|
84
|
-
static escapeReference(reference) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// The empty reference is written as a bare delimiter pair, so that it reads
|
|
90
|
-
// back as itself instead of disappearing from the document.
|
|
91
|
-
if (reference === '') {
|
|
92
|
-
return '""';
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const hasSingleQuote = reference.includes("'");
|
|
96
|
-
const hasDoubleQuote = reference.includes('"');
|
|
97
|
-
|
|
98
|
-
// A reference that begins with a `#` has to be quoted, or it would read
|
|
99
|
-
// back as a comment. A `#` anywhere else in a reference is content
|
|
100
|
-
// (`issue#1047`), so only the first character matters.
|
|
101
|
-
const needsQuoting =
|
|
102
|
-
reference.startsWith('#') ||
|
|
103
|
-
reference.includes(':') ||
|
|
104
|
-
reference.includes('(') ||
|
|
105
|
-
reference.includes(')') ||
|
|
106
|
-
reference.includes(' ') ||
|
|
107
|
-
reference.includes('\t') ||
|
|
108
|
-
reference.includes('\n') ||
|
|
109
|
-
reference.includes('\r') ||
|
|
110
|
-
hasDoubleQuote ||
|
|
111
|
-
hasSingleQuote;
|
|
112
|
-
|
|
113
|
-
// Handle edge case: reference contains both single and double quotes
|
|
114
|
-
if (hasSingleQuote && hasDoubleQuote) {
|
|
115
|
-
// Escape single quotes and wrap in single quotes
|
|
116
|
-
return `'${reference.replace(/'/g, "\\'")}'`;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Prefer single quotes if double quotes are present
|
|
120
|
-
if (hasDoubleQuote) {
|
|
121
|
-
return `'${reference}'`;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Use double quotes if single quotes are present
|
|
125
|
-
if (hasSingleQuote) {
|
|
126
|
-
return `"${reference}"`;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Use single quotes for special characters
|
|
130
|
-
if (needsQuoting) {
|
|
131
|
-
return `'${reference}'`;
|
|
132
|
-
}
|
|
87
|
+
static escapeReference(reference, options = {}) {
|
|
88
|
+
return escapeReference(reference, options);
|
|
89
|
+
}
|
|
133
90
|
|
|
134
|
-
|
|
135
|
-
|
|
91
|
+
/** Decode an escaped reference, rather than an already parsed Link ID. */
|
|
92
|
+
static unescapeReference(reference) {
|
|
93
|
+
return unescapeReference(reference);
|
|
136
94
|
}
|
|
137
95
|
|
|
138
96
|
/**
|
package/src/Parser.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Link } from './Link.js';
|
|
2
|
+
import { LinksGroup } from './LinksGroup.js';
|
|
2
3
|
import { ParseError } from './ParseError.js';
|
|
3
4
|
import { stripComments } from './comments.js';
|
|
4
5
|
import * as parserModule from './parser-generated.js';
|
|
@@ -31,6 +32,30 @@ export class Parser {
|
|
|
31
32
|
* @throws {Error} If parsing fails
|
|
32
33
|
*/
|
|
33
34
|
parse(input) {
|
|
35
|
+
return this._parse(input, (raw) => this.transformResult(raw));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Read the indentation tree before it is expanded into path combinations.
|
|
40
|
+
* Each group's element is the Link on that line; its children are groups
|
|
41
|
+
* for the immediately indented lines. Parenthesized values remain Links.
|
|
42
|
+
* @param {string} input - The Lino document
|
|
43
|
+
* @returns {LinksGroup[]}
|
|
44
|
+
*/
|
|
45
|
+
parseGroups(input) {
|
|
46
|
+
return this._parse(input, (raw) =>
|
|
47
|
+
raw.map((item) => this._transformGroup(item))
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
_transformGroup(item) {
|
|
52
|
+
return new LinksGroup(
|
|
53
|
+
this.transformLink(item),
|
|
54
|
+
(item.children || []).map((child) => this._transformGroup(child))
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_parse(input, transform) {
|
|
34
59
|
// Validate input
|
|
35
60
|
if (typeof input !== 'string') {
|
|
36
61
|
throw new TypeError('Input must be a string');
|
|
@@ -50,7 +75,7 @@ export class Parser {
|
|
|
50
75
|
const rawResult = parserModule.parse(prepared, {
|
|
51
76
|
maxDepth: this.maxDepth,
|
|
52
77
|
});
|
|
53
|
-
return
|
|
78
|
+
return transform(rawResult);
|
|
54
79
|
} catch (error) {
|
|
55
80
|
// A syntax error knows where it stopped; anything else is passed on with
|
|
56
81
|
// the original error kept as the cause.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/* global TextEncoder, TextDecoder */
|
|
2
|
+
|
|
3
|
+
/** Detect the reserved versioned-literal prefix at a reference boundary. */
|
|
4
|
+
export function hasReferenceLiteralPrefix(text, start = 0) {
|
|
5
|
+
return /^~[0-9]+\{/.test(text.slice(start));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Encode exact Unicode text as a version 1 UTF-8 hexadecimal reference. */
|
|
9
|
+
export function encodeReferenceLiteral(text) {
|
|
10
|
+
if (typeof text !== 'string' || !text.isWellFormed()) {
|
|
11
|
+
throw new TypeError('Reference must be well-formed Unicode text');
|
|
12
|
+
}
|
|
13
|
+
const hex = Array.from(new TextEncoder().encode(text), (byte) =>
|
|
14
|
+
byte.toString(16).padStart(2, '0')
|
|
15
|
+
).join('');
|
|
16
|
+
return `~1{${hex}}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Decode a complete versioned literal, rejecting malformed bytes and versions. */
|
|
20
|
+
export function decodeReferenceLiteral(literal) {
|
|
21
|
+
const match = /^~([0-9]+)\{([0-9a-fA-F]*)\}$/.exec(literal);
|
|
22
|
+
if (
|
|
23
|
+
!match ||
|
|
24
|
+
match[0] !== literal ||
|
|
25
|
+
match[1] !== '1' ||
|
|
26
|
+
match[2].length % 2 !== 0
|
|
27
|
+
) {
|
|
28
|
+
throw new TypeError(
|
|
29
|
+
'Invalid or unsupported reference literal (expected ~1{UTF-8 hex})'
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(
|
|
33
|
+
Uint8Array.from(match[2].match(/../g) ?? [], (hex) => parseInt(hex, 16))
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Use readable legacy quoting where possible and a literal for empty/control text. */
|
|
38
|
+
export function formatReference(text) {
|
|
39
|
+
if (typeof text !== 'string' || !text.isWellFormed()) {
|
|
40
|
+
throw new TypeError('Reference must be well-formed Unicode text');
|
|
41
|
+
}
|
|
42
|
+
if (!text || /[\u0000-\u001f\u007f]/u.test(text)) {
|
|
43
|
+
return encodeReferenceLiteral(text);
|
|
44
|
+
}
|
|
45
|
+
if (
|
|
46
|
+
!text.startsWith('#') &&
|
|
47
|
+
!/^~[0-9]+\{/.test(text) &&
|
|
48
|
+
!/[\p{White_Space}\ufeff():"'`]/u.test(text)
|
|
49
|
+
) {
|
|
50
|
+
return text;
|
|
51
|
+
}
|
|
52
|
+
let choice = null;
|
|
53
|
+
for (const quote of ["'", '"', '`']) {
|
|
54
|
+
if (text.startsWith(quote)) continue;
|
|
55
|
+
let longest = 0;
|
|
56
|
+
let run = 0;
|
|
57
|
+
for (const character of text) {
|
|
58
|
+
run = character === quote ? run + 1 : 0;
|
|
59
|
+
longest = Math.max(longest, run);
|
|
60
|
+
}
|
|
61
|
+
const count = (longest + 1) | 1;
|
|
62
|
+
if (choice === null || count < choice.count) choice = { quote, count };
|
|
63
|
+
}
|
|
64
|
+
const delimiter = choice.quote.repeat(choice.count);
|
|
65
|
+
return delimiter + text + delimiter;
|
|
66
|
+
}
|
package/src/grammar.pegjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{{
|
|
2
2
|
import { DelimitedReferences } from './quotes.js';
|
|
3
|
+
import { decodeReferenceLiteral, hasReferenceLiteralPrefix } from './ReferenceLiteral.js';
|
|
3
4
|
}}
|
|
4
5
|
|
|
5
6
|
{
|
|
@@ -217,9 +218,18 @@ indentedIdLink = id:reference __ ":" eol { return { id: id, values: [] }; }
|
|
|
217
218
|
|
|
218
219
|
// Reference can be quoted (with any number of quotes N >= 1) or simple unquoted
|
|
219
220
|
// Universal approach: use procedural parsing for all quote types and counts
|
|
220
|
-
reference = quotedReference / simpleReference
|
|
221
|
+
reference = referenceLiteral / quotedReference / simpleReference
|
|
222
|
+
|
|
223
|
+
// Versioned literals reserve ~<version>{<UTF-8 hex>} at a reference boundary.
|
|
224
|
+
// A malformed reserved token must fail rather than fall back to a bare name.
|
|
225
|
+
referenceLiteral = &'~' &{
|
|
226
|
+
return hasReferenceLiteralPrefix(input, offset());
|
|
227
|
+
} literal:$('~' [0-9]+ '{' [^} \t\r\n()]* '}') {
|
|
228
|
+
return decodeReferenceLiteral(literal);
|
|
229
|
+
}
|
|
221
230
|
|
|
222
|
-
simpleReference =
|
|
231
|
+
simpleReference = &{ return !hasReferenceLiteralPrefix(input, offset()); }
|
|
232
|
+
chars:referenceSymbol+ { return chars.join(''); }
|
|
223
233
|
|
|
224
234
|
// Universal quoted reference - handles any N quotes for all quote types
|
|
225
235
|
// Uses procedural parsing with input/offset() for clean, simple logic
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
export { Link, formatLinks } from './Link.js';
|
|
2
|
+
export { escapeReference, unescapeReference } from './references.js';
|
|
3
|
+
export {
|
|
4
|
+
parseIndentedDocument,
|
|
5
|
+
formatIndentedDocument,
|
|
6
|
+
} from './IndentedDocument.js';
|
|
7
|
+
export {
|
|
8
|
+
encodeReferenceLiteral,
|
|
9
|
+
decodeReferenceLiteral,
|
|
10
|
+
} from './ReferenceLiteral.js';
|
|
2
11
|
export { LinksGroup } from './LinksGroup.js';
|
|
3
12
|
export { Parser, DEFAULT_MAX_DEPTH } from './Parser.js';
|
|
4
13
|
export { StreamParseError, StreamParser } from './StreamParser.js';
|