UNPKG

json-stream-es

Version:

A streaming JSON parser/stringifier using web streams.

1 lines 108 kB
{"version":3,"file":"json-stream-es.mjs","sources":["../src/types.ts","../src/utils.ts","../src/json-deserializer.ts","../src/json-parser.ts","../src/json-serializer.ts","../src/json-stringifier.ts","../src/json-path-detector.ts","../src/json-path-selector.ts","../src/stream-splitter.ts","../src/json-path-stream-splitter.ts","../src/convenience.ts"],"sourcesContent":["/** A JavaScript value that can be stringified to JSON. */\nexport type JsonValue = { [key: string]: JsonValue } | Array<JsonValue> | string | number | boolean | null;\n\nexport enum JsonChunkType {\n\t/** A whitespace that appears between JSON tokens and has no semantic meaning. */\n\tWHITESPACE = \"WHITESPACE\",\n\n\t/** A comma that separates two array/object items. */\n\tCOMMA = \"COMMA\",\n\n\t/** A colon that separates an object key from its value. */\n\tCOLON = \"COLON\",\n\n\t/**\n\t * The start of an object, represented by a curly open bracket. Will be followed by zero or more properties, each one represented\n\t * by a string key (one STRING_KEY_START, zero or more STRING_KEY_CHUNKs, one STRING_KEY_END) for the key, a\n\t * COLON, a series of chunks for the value and a COMMA (except for the last property); and finally an OBJECT_END.\n\t */\n\tOBJECT_START = \"OBJECT_START\",\n\n\t/** The end of an object, represented by a curly close bracket. */\n\tOBJECT_END = \"OBJECT_END\",\n\n\t/**\n\t * The start of an array, represented by a square open bracket. Will be followed by zero or more chunks representing the values,\n\t * each followed by a COMMA (except the last one); and finally an ARRAY_END.\n\t */\n\tARRAY_START = \"ARRAY_START\",\n\n\t/** The end of an array, represented by a square close bracket. */\n\tARRAY_END = \"ARRAY_END\",\n\n\t/**\n\t * The start of a string, represented by a double quote. Will be followed by zero or more STRING_CHUNKs and finally\n\t * a STRING_END.\n\t */\n\tSTRING_START = \"STRING_START\",\n\n\t/**\n\t * A section of a string value. Unicode characters are always fully included, so an escape value like \\uffff will never span across multiple chunks.\n\t */\n\tSTRING_CHUNK = \"STRING_CHUNK\",\n\n\t/** The end of a string value, represented by a double quote. */\n\tSTRING_END = \"STRING_END\",\n\n\t/** A number. May be positive/negative and an integer/float, and the raw value can have an exponent. */\n\tNUMBER_VALUE = \"NUMBER_VALUE\",\n\n\t/** A boolean, either true or false. */\n\tBOOLEAN_VALUE = \"BOOLEAN_VALUE\",\n\n\t/** A null value. */\n\tNULL_VALUE = \"NULL_VALUE\"\n}\n\nexport enum StringRole {\n\t/** A string used as a property key inside an object. */\n\tKEY = \"KEY\",\n\t/** A string used as a value. */\n\tVALUE = \"VALUE\"\n}\n\nexport type JsonChunk<Type extends JsonChunkType = JsonChunkType> = Extract<(\n\t| { type: JsonChunkType.WHITESPACE }\n\t| { type: JsonChunkType.COMMA }\n\t| { type: JsonChunkType.COLON }\n\t| { type: JsonChunkType.OBJECT_START }\n\t| { type: JsonChunkType.OBJECT_END }\n\t| { type: JsonChunkType.ARRAY_START }\n\t| { type: JsonChunkType.ARRAY_END }\n\t| { type: JsonChunkType.STRING_START; role: StringRole }\n\t| {\n\t\ttype: JsonChunkType.STRING_CHUNK;\n\t\trole: StringRole;\n\t\t/** The string value of the string value, without quotes and with backslash escapes resolved. */\n\t\tvalue: string;\n\t}\n\t| { type: JsonChunkType.STRING_END; role: StringRole }\n\t| { type: JsonChunkType.NUMBER_VALUE; value: number }\n\t| { type: JsonChunkType.BOOLEAN_VALUE; value: boolean }\n\t| { type: JsonChunkType.NULL_VALUE; value: null }\n) & {\n\t/** The raw JSON code for this chunk. The concatenated rawValues of all chunks form a valid JSON value. */\n\trawValue: string;\n}, { type: Type }>;\n\nexport function whitespace(rawValue: string): JsonChunk<JsonChunkType.WHITESPACE> {\n\treturn {\n\t\ttype: JsonChunkType.WHITESPACE,\n\t\trawValue\n\t};\n}\n\nexport function comma(rawValue = \",\"): JsonChunk<JsonChunkType.COMMA> {\n\treturn {\n\t\ttype: JsonChunkType.COMMA,\n\t\trawValue\n\t};\n}\n\nexport function colon(rawValue = \":\"): JsonChunk<JsonChunkType.COLON> {\n\treturn {\n\t\ttype: JsonChunkType.COLON,\n\t\trawValue\n\t};\n}\n\nexport function objectStart(rawValue = \"{\"): JsonChunk<JsonChunkType.OBJECT_START> {\n\treturn {\n\t\ttype: JsonChunkType.OBJECT_START,\n\t\trawValue\n\t};\n}\n\nexport function objectEnd(rawValue = \"}\"): JsonChunk<JsonChunkType.OBJECT_END> {\n\treturn {\n\t\ttype: JsonChunkType.OBJECT_END,\n\t\trawValue\n\t};\n}\n\nexport function arrayStart(rawValue = \"[\"): JsonChunk<JsonChunkType.ARRAY_START> {\n\treturn {\n\t\ttype: JsonChunkType.ARRAY_START,\n\t\trawValue\n\t};\n}\n\nexport function arrayEnd(rawValue = \"]\"): JsonChunk<JsonChunkType.ARRAY_END> {\n\treturn {\n\t\ttype: JsonChunkType.ARRAY_END,\n\t\trawValue\n\t};\n}\n\nexport function stringStart(role = StringRole.VALUE, rawValue = \"\\\"\"): JsonChunk<JsonChunkType.STRING_START> {\n\treturn {\n\t\ttype: JsonChunkType.STRING_START,\n\t\trole,\n\t\trawValue\n\t};\n}\n\nexport function stringChunk(value: string, role = StringRole.VALUE, rawValue?: string): JsonChunk<JsonChunkType.STRING_CHUNK> {\n\treturn {\n\t\ttype: JsonChunkType.STRING_CHUNK,\n\t\trole,\n\t\tvalue,\n\t\trawValue: rawValue ?? JSON.stringify(value).slice(1, -1)\n\t};\n}\n\nexport function stringEnd(role = StringRole.VALUE, rawValue = \"\\\"\"): JsonChunk<JsonChunkType.STRING_END> {\n\treturn {\n\t\ttype: JsonChunkType.STRING_END,\n\t\trole,\n\t\trawValue\n\t};\n}\n\nexport function numberValue(value: number, rawValue?: string): JsonChunk<JsonChunkType.NUMBER_VALUE> {\n\treturn {\n\t\ttype: JsonChunkType.NUMBER_VALUE,\n\t\tvalue,\n\t\trawValue: rawValue ?? JSON.stringify(value)\n\t};\n}\n\nexport function booleanValue(value: boolean, rawValue?: string): JsonChunk<JsonChunkType.BOOLEAN_VALUE> {\n\treturn {\n\t\ttype: JsonChunkType.BOOLEAN_VALUE,\n\t\tvalue,\n\t\trawValue: rawValue ?? JSON.stringify(value)\n\t};\n}\n\nexport function nullValue(rawValue = \"null\"): JsonChunk<JsonChunkType.NULL_VALUE> {\n\treturn {\n\t\ttype: JsonChunkType.NULL_VALUE,\n\t\tvalue: null,\n\t\trawValue\n\t};\n}","/**\n * Converts a ReadableStream into an AsyncIterable so the stream can be consumed using \"for await\".\n * In the latest Streams API, ReadableStream is an AsyncIterable, but not all browsers support this yet.\n */\nexport async function* streamToIterable<T>(stream: ReadableStream<T>): AsyncGenerator<T, void, undefined> {\n\tconst reader = stream.getReader();\n\n\ttry {\n\t\twhile (true) {\n\t\t\tconst { done, value } = await reader.read();\n\t\t\tif (done) {\n\t\t\t\treturn;\n\t\t\t} else {\n\t\t\t\tyield value;\n\t\t\t}\n\t\t}\n\t} finally {\n\t\t// Is also called if iterator is quit early (by using break;)\n\t\treader.cancel().catch(() => undefined);\n\t}\n}\n\n/**\n * Converts a sync/async iterable into an UnderlyingDefaultSource, which can be used as the argument to construct a ReadableStream.\n */\nexport function iterableToSource<T>(iterable: AsyncIterable<T> | Iterable<T>): UnderlyingDefaultSource<T> {\n\tconst iterator = Symbol.asyncIterator in iterable ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator]();\n\treturn {\n\t\tasync pull(controller) {\n\t\t\tconst { value, done } = await iterator.next();\n\t\t\tif (done) {\n\t\t\t\tcontroller.close();\n\t\t\t} else {\n\t\t\t\tcontroller.enqueue(value);\n\t\t\t}\n\t\t},\n\t};\n}\n\nexport function iterableToStream<T>(iterable: AsyncIterable<T> | Iterable<T>, strategy?: QueuingStrategy<T>): ReadableStream<T> {\n\treturn new ReadableStream<T>(iterableToSource(iterable), strategy);\n}\n\nexport async function streamToArray<T>(stream: ReadableStream<T>): Promise<T[]> {\n\tconst reader = stream.getReader();\n\tconst result: T[] = [];\n\n\t// eslint-disable-next-line no-constant-condition\n\twhile (true) {\n\t\tconst { done, value } = await reader.read();\n\t\tif (done) {\n\t\t\treturn result;\n\t\t} else {\n\t\t\tresult.push(value);\n\t\t}\n\t}\n}\n\nexport async function streamToString(stream: ReadableStream<string>): Promise<string> {\n\treturn (await streamToArray(stream)).join(\"\");\n}\n\nexport function stringToStream(string: string): ReadableStream<string> {\n\treturn iterableToStream([string]);\n}\n\nexport function concatStreams<T>(...streams: Array<ReadableStream<T> | (() => ReadableStream<T>)>): ReadableStream<T> {\n\tconst transform = new TransformStream();\n\t(async () => {\n\t\tfor (const stream of streams) {\n\t\t\tawait (typeof stream === \"function\" ? stream() : stream).pipeTo(transform.writable, { preventClose: true });\n\t\t}\n\t\tawait transform.writable.close();\n\t})().catch(async (err) => {\n\t\tawait transform.writable.abort(err);\n\t});\n\treturn transform.readable;\n}\n\nexport type TransformerAbortCallback<O> = (reason: any, controller: TransformStreamDefaultController<O>) => void | PromiseLike<void>;\n\n/**\n * A transform stream that provides an abort() handler to transform stream abortions.\n * More info can be found on https://stackoverflow.com/a/78489418/242365.\n */\nexport class AbortHandlingTransformStream<I, O> extends TransformStream<I, O> {\n constructor(\n transformer?: Transformer<I, O> & {\n abort?: TransformerAbortCallback<O>;\n },\n writableStrategy?: QueuingStrategy<I>,\n readableStrategy?: QueuingStrategy<O>\n ) {\n const { abort, start, ...rest } = transformer ?? {};\n let controller: TransformStreamDefaultController<O>;\n super({\n ...rest,\n start: (c) => {\n controller = c;\n start?.(c);\n }\n }, writableStrategy, readableStrategy);\n\n const writer = this.writable.getWriter();\n const writable = new WritableStream({\n write: (chunk) => writer.write(chunk),\n close: () => writer.close(),\n abort: async (reason) => {\n if (abort) {\n try {\n await abort(reason, controller);\n } catch (err: any) {\n await writer.abort(err);\n }\n } else {\n await writer.abort(reason);\n }\n }\n });\n\n Object.defineProperty(this, \"writable\", {\n get: () => writable,\n\t\t\tconfigurable: true\n });\n }\n}\n\n/**\n * A transform stream where rather than specifying a transformer as a constructor argument, you override its methods to implement the\n * transformation.\n */\nexport abstract class AbstractTransformStream<I, O> extends AbortHandlingTransformStream<I, O> {\n\tconstructor(writableStrategy?: QueuingStrategy<I>, readableStrategy?: QueuingStrategy<O>) {\n\t\tsuper({\n\t\t\ttransform: (chunk, controller) => {\n\t\t\t\treturn this.transform(chunk, controller);\n\t\t\t},\n\t\t\tflush: (controller) => {\n\t\t\t\treturn this.flush(controller);\n\t\t\t},\n\t\t\tabort: (reason, controller) => {\n\t\t\t\treturn this.abort(reason, controller);\n\t\t\t}\n\t\t}, writableStrategy, readableStrategy);\n\t}\n\n\tprotected abstract transform(chunk: I, controller: TransformStreamDefaultController<O>): void | Promise<void>;\n\n\tprotected flush(controller: TransformStreamDefaultController<O>): void | Promise<void> {\n\t\tcontroller.terminate();\n\t}\n\n\tprotected abort(reason: any, controller: TransformStreamDefaultController<O>): void | Promise<void> {\n\t\tcontroller.error(reason);\n\t}\n}\n\n/**\n * A TransformStream that is set up by providing a ReadableStream mapper rather than transforming individual chunks using\n * start(), transform() and flush().\n * This allows access to ReadableStream methods such as pipeThrough(), which makes it easy to reuse other TransformStreams\n * in the implementation.\n * @param transformReadable Retrieves one parameter with a ReadableStream that emits the input data of the TransformStream.\n * Should return a ReadableStream whose output will become the output data of the TransformStream.\n */\n// https://stackoverflow.com/a/78404600/242365\nexport class PipeableTransformStream<I, O> extends TransformStream<I, O> {\n\tconstructor(transformReadable: (readable: ReadableStream<I>) => ReadableStream<O>, writableStrategy?: QueuingStrategy<I>, readableStrategy?: QueuingStrategy<O>) {\n\t\tsuper({}, writableStrategy);\n\t\tconst readable = transformReadable(this.readable as any).pipeThrough(new TransformStream({}, undefined, readableStrategy));\n\t\tObject.defineProperty(this, \"readable\", { get: () => readable });\n\t}\n}\n\nexport function arrayStartsWith<T>(array: T[], startsWith: T[]): boolean {\n\treturn array.length >= startsWith.length && startsWith.every((v, i) => array[i] === v);\n}","import type { JsonChunkWithPath, JsonPath } from \"./json-path-detector\";\nimport { JsonChunkType, StringRole, type JsonChunk, type JsonValue } from \"./types\";\nimport { AbstractTransformStream } from \"./utils\";\n\nenum StateType {\n\tROOT = \"ROOT\",\n\tOBJECT_PROPERTY = \"OBJECT_PROPERTY\",\n\tARRAY_ITEM = \"ARRAY_ITEM\"\n};\n\ntype AnyState<C extends JsonChunk & { path?: JsonPath }> = (\n\t{\n\t\ttype: StateType.ROOT;\n\t\tvalue: JsonValue | undefined;\n\t\tpath: C[\"path\"];\n\t} | {\n\t\ttype: StateType.OBJECT_PROPERTY;\n\t\tobject: Record<string, JsonValue>;\n\t\tkey: string;\n\t\tvalue: JsonValue | undefined;\n\t\tparent: State<C>;\n\t} | {\n\t\ttype: StateType.ARRAY_ITEM;\n\t\tarray: Array<JsonValue>;\n\t\tvalue: JsonValue | undefined;\n\t\tparent: State<C>\n\t}\n);\n\ntype State<C extends JsonChunk & { path?: JsonPath }, Type extends StateType = StateType> = Extract<AnyState<C>, { type: Type }>;\n\nexport type JsonValueAndOptionalPath<C extends JsonChunk & { path?: JsonPath }> = { value: JsonValue; path: C[\"path\"] };\nexport type JsonValueAndPath = JsonValueAndOptionalPath<JsonChunkWithPath>;\n\n/**\n * Converts a stream of JsonChunks into JsonValues. The input stream may contain multiple JSON documents on the root level, as\n * produced by JsonPathSelector or by concatenating multiple JsonChunk streams.\n */\nexport class JsonDeserializer<C extends JsonChunk & { path?: JsonPath } = JsonChunkWithPath> extends AbstractTransformStream<C, JsonValueAndOptionalPath<C>> {\n\tprotected state: State<C> = { type: StateType.ROOT, value: undefined, path: [] };\n\n\tconstructor() {\n\t\tsuper();\n\t}\n\n\tprotected handleValueEnd(controller: TransformStreamDefaultController<JsonValueAndOptionalPath<C>>): void {\n\t\tif (this.state.type === StateType.ROOT) {\n\t\t\tif (this.state.value !== undefined) {\n\t\t\t\tcontroller.enqueue({ value: this.state.value, path: this.state.path });\n\t\t\t}\n\t\t\tthis.state.value = undefined;\n\t\t} else if (this.state.type === StateType.OBJECT_PROPERTY) {\n\t\t\tif (this.state.value !== undefined) {\n\t\t\t\tthis.state.object[this.state.key] = this.state.value;\n\t\t\t}\n\t\t\tthis.state.key = \"\";\n\t\t\tthis.state.value = undefined;\n\t\t} else if (this.state.type === StateType.ARRAY_ITEM) {\n\t\t\tif (this.state.value !== undefined) {\n\t\t\t\tthis.state.array.push(this.state.value);\n\t\t\t}\n\t\t\tthis.state.value = undefined;\n\t\t}\n\t}\n\n\tprotected override transform(chunk: C, controller: TransformStreamDefaultController<JsonValueAndOptionalPath<C>>): void {\n\t\tif (chunk.type === JsonChunkType.NUMBER_VALUE || chunk.type === JsonChunkType.BOOLEAN_VALUE || chunk.type === JsonChunkType.NULL_VALUE) {\n\t\t\tthis.state.value = chunk.value;\n\t\t\tif (this.state.type === StateType.ROOT) {\n\t\t\t\tthis.state.path = chunk.path;\n\t\t\t}\n\t\t\tthis.handleValueEnd(controller);\n\t\t}\n\n\t\telse if (chunk.type === JsonChunkType.STRING_START && chunk.role === StringRole.VALUE) {\n\t\t\tthis.state.value = \"\";\n\t\t\tif (this.state.type === StateType.ROOT) {\n\t\t\t\tthis.state.path = chunk.path;\n\t\t\t}\n\t\t}\n\t\telse if (chunk.type === JsonChunkType.STRING_CHUNK && chunk.role === StringRole.VALUE) {\n\t\t\tthis.state.value += chunk.value;\n\t\t}\n\t\telse if (chunk.type === JsonChunkType.STRING_END && chunk.role === StringRole.VALUE) {\n\t\t\tthis.handleValueEnd(controller);\n\t\t}\n\n\t\telse if (chunk.type === JsonChunkType.ARRAY_START) {\n\t\t\tthis.state.value = [];\n\t\t\tif (this.state.type === StateType.ROOT) {\n\t\t\t\tthis.state.path = chunk.path;\n\t\t\t}\n\t\t\tthis.state = {\n\t\t\t\ttype: StateType.ARRAY_ITEM,\n\t\t\t\tarray: this.state.value,\n\t\t\t\tvalue: undefined,\n\t\t\t\tparent: this.state\n\t\t\t};\n\t\t} else if (chunk.type === JsonChunkType.ARRAY_END && this.state.type === StateType.ARRAY_ITEM) {\n\t\t\tthis.state = this.state.parent;\n\t\t\tthis.handleValueEnd(controller);\n\t\t}\n\n\t\telse if (chunk.type === JsonChunkType.OBJECT_START) {\n\t\t\tthis.state.value = {};\n\t\t\tif (this.state.type === StateType.ROOT) {\n\t\t\t\tthis.state.path = chunk.path;\n\t\t\t}\n\t\t\tthis.state = {\n\t\t\t\ttype: StateType.OBJECT_PROPERTY,\n\t\t\t\tobject: this.state.value,\n\t\t\t\tkey: \"\",\n\t\t\t\tvalue: undefined,\n\t\t\t\tparent: this.state\n\t\t\t};\n\t\t} else if (chunk.type === JsonChunkType.OBJECT_END && this.state.type === StateType.OBJECT_PROPERTY) {\n\t\t\tthis.state = this.state.parent;\n\t\t\tthis.handleValueEnd(controller);\n\t\t} else if (chunk.type === JsonChunkType.STRING_CHUNK && chunk.role === StringRole.KEY && this.state.type === StateType.OBJECT_PROPERTY) {\n\t\t\tthis.state.key += chunk.value;\n\t\t}\n\t}\n}\n\n/**\n * Converts a stream of JsonChunks into a single JsonValue. The input stream must contain exactly one JSON documents on the root level.\n */\nexport async function deserializeJsonValue(stream: ReadableStream<JsonChunk>): Promise<JsonValue> {\n\tconst reader = stream.pipeThrough(new JsonDeserializer()).getReader();\n\tconst { value, done: done1 } = await reader.read();\n\tif (done1) {\n\t\tthrow new Error(\"The stream did not contain any values.\");\n\t}\n\tconst { done: done2 } = await reader.read();\n\tif (!done2) {\n\t\treader.cancel().catch(() => undefined);\n\t\tthrow new Error(\"The stream contained more than one value.\");\n\t}\n\treturn value.value;\n}","import { StringRole, arrayEnd, arrayStart, booleanValue, colon, comma, nullValue, numberValue, objectEnd, objectStart, stringChunk, stringEnd, stringStart, whitespace, type JsonChunk } from \"./types\";\nimport { AbstractTransformStream } from \"./utils\";\n\nenum StateType {\n\tSTART = \"start\",\n\tOBJECT_AFTER_START = \"object_after_start\", OBJECT_AFTER_KEY = \"object_after_key\", OBJECT_AFTER_COLON = \"object_after_colon\", OBJECT_AFTER_VALUE = \"object_after_value\", OBJECT_AFTER_COMMA = \"object_after_comma\",\n\tARRAY_AFTER_START = \"array_after_start\", ARRAY_AFTER_VALUE = \"array_after_value\", ARRAY_AFTER_COMMA = \"array_after_comma\",\n\tBOOLEAN_OR_NULL = \"boolean_or_null\",\n\tNUMBER_MINUS = \"number_minus\", NUMBER_DIGITS = \"number_digits\", NUMBER_POINT = \"number_point\", NUMBER_DECIMAL_DIGITS = \"number_decimal_digits\",\n\tNUMBER_E = \"number_e\", NUMBER_E_PLUSMINUS = \"number_e_plusminus\", NUMBER_E_DIGITS = \"number_e_digits\",\n\tWHITESPACE = \"whitespace\",\n\tSTRING = \"string\", STRING_AFTER_BACKSLASH = \"string_after_backslash\", STRING_AFTER_BACKSLASH_U = \"string_after_backslash_u\",\n\tEND = \"end\"\n};\n\n/** States where the start of a new value (object/array/string/number/boolean/null) is allowed. */\nconst VALUE_START_ALLOWED = [StateType.START, StateType.OBJECT_AFTER_COLON, StateType.ARRAY_AFTER_START, StateType.ARRAY_AFTER_COMMA] as const;\nconst VALUE_START_ALLOWED_MULTI = [...VALUE_START_ALLOWED, StateType.END] as const;\n/** States whree the start of an object key string is allowed. */\nconst KEY_START_ALLOWED = [StateType.OBJECT_AFTER_START, StateType.OBJECT_AFTER_COMMA] as const;\n/** States where a whilespace character is allowed. */\nconst WHITESPACE_ALLOWED = [\n\tStateType.START, StateType.OBJECT_AFTER_START, StateType.OBJECT_AFTER_KEY, StateType.OBJECT_AFTER_COLON, StateType.OBJECT_AFTER_VALUE, StateType.OBJECT_AFTER_COMMA,\n\tStateType.ARRAY_AFTER_START, StateType.ARRAY_AFTER_VALUE, StateType.ARRAY_AFTER_COMMA,\n\tStateType.END\n] as const;\n\ntype AnyState = {\n\ttype: StateType.START | StateType.END\n} | {\n\ttype: (\n\t\t| StateType.OBJECT_AFTER_START | StateType.OBJECT_AFTER_KEY | StateType.OBJECT_AFTER_COLON | StateType.OBJECT_AFTER_VALUE\n\t\t| StateType.OBJECT_AFTER_COMMA | StateType.ARRAY_AFTER_START | StateType.ARRAY_AFTER_VALUE | StateType.ARRAY_AFTER_COMMA\n\t);\n\tparentState: State<typeof VALUE_START_ALLOWED_MULTI[number]>;\n} | {\n\ttype: StateType.BOOLEAN_OR_NULL;\n\trawValue: string;\n\tparentState: State<typeof VALUE_START_ALLOWED_MULTI[number]>;\n} | {\n\ttype: StateType.WHITESPACE;\n\trawValue: string;\n\tparentState: State<typeof WHITESPACE_ALLOWED[number]>;\n} | {\n\ttype: (\n\t\t| StateType.NUMBER_MINUS | StateType.NUMBER_DIGITS | StateType.NUMBER_POINT | StateType.NUMBER_DECIMAL_DIGITS\n\t\t| StateType.NUMBER_E | StateType.NUMBER_E_PLUSMINUS | StateType.NUMBER_E_DIGITS\n\t);\n\trawValue: string;\n\tparentState: State<typeof VALUE_START_ALLOWED_MULTI[number]>;\n} | {\n\ttype: StateType.STRING;\n\tvalue: string;\n\trawValue: string;\n\trole: StringRole;\n\tparentState: State<typeof VALUE_START_ALLOWED_MULTI[number] | typeof KEY_START_ALLOWED[number]>;\n} | {\n\ttype: StateType.STRING_AFTER_BACKSLASH;\n\trawValue: string;\n\tparentState: State<StateType.STRING>;\n} | {\n\ttype: StateType.STRING_AFTER_BACKSLASH_U;\n\t/** The unicode hex code */\n\tvalue: string;\n\trawValue: string;\n\tparentState: State<StateType.STRING>;\n};\n\ntype State<T extends StateType = StateType> = AnyState & { type: T };\n\n/** Type guard to check whether the given state has any of the given types. */\nfunction isState<T extends StateType>(state: State, types: readonly [...T[]]): state is State & { type: T } {\n\treturn (types as ReadonlyArray<StateType>).includes(state.type);\n}\n\n/**\n * Given the state when a value (object/array/string/number/boolean/null) was started, returns the\n * new state after the value was finished.\n */\nfunction getStateAfterValue(stateBeforeValue: State<typeof VALUE_START_ALLOWED_MULTI[number] | typeof KEY_START_ALLOWED[number]>): State {\n\tif (isState(stateBeforeValue, [StateType.START, StateType.END])) {\n\t\treturn { ...stateBeforeValue, type: StateType.END };\n\t} else if (stateBeforeValue.type === StateType.OBJECT_AFTER_COLON) {\n\t\treturn { ...stateBeforeValue, type: StateType.OBJECT_AFTER_VALUE };\n\t} else if (isState(stateBeforeValue, [StateType.ARRAY_AFTER_START, StateType.ARRAY_AFTER_COMMA])) {\n\t\treturn { ...stateBeforeValue, type: StateType.ARRAY_AFTER_VALUE };\n\t} else if (isState(stateBeforeValue, [StateType.OBJECT_AFTER_START, StateType.OBJECT_AFTER_COMMA])) {\n\t\treturn { ...stateBeforeValue, type: StateType.OBJECT_AFTER_KEY };\n\t} else {\n\t\tthrow new Error(`Invalid value state ${stateBeforeValue.type}.`);\n\t}\n}\n\ntype Context = {\n\tchar: string;\n\tposition: number;\n};\n\nexport class UnexpectedCharError extends Error {\n\tconstructor(context: Context) {\n\t\tsuper(`Unexpected character \"${context.char}\" at position ${context.position}.`);\n\t}\n}\n\nexport class PrematureEndError extends Error {\n\tconstructor() {\n\t\tsuper(\"Premature end of JSON stream.\");\n\t}\n}\n\n/**\n * Each character that is possible after a \\ inside a string, mapped to the character that it replaces.\n */\nconst STRING_ESCAPE_CHARS = {\n\t\"\\\"\": \"\\\"\",\n\t\"\\\\\": \"\\\\\",\n\t\"/\": \"/\",\n\t\"b\": \"\\b\",\n\t\"f\": \"\\f\",\n\t\"n\": \"\\n\",\n\t\"r\": \"\\r\",\n\t\"t\": \"\\t\"\n};\n\n/** Whitespace characters allowed between tokens. */\nconst WHITESPACE_CHARS = [\" \", \"\\t\", \"\\n\", \"\\r\"];\n/** Record separator char that is ignored between JSON documents in multi mode. */\nconst RS_CHARS = [\"\\x1e\"];\nconst NUMBER_CHARS = [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\"];\nconst HEX_NUMBER_CHARS = [...NUMBER_CHARS, \"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"A\", \"B\", \"C\", \"D\", \"E\", \"F\"];\n\nconst BOOLEAN_OR_NULL = { false: false, true: true, null: null };\nconst BOOLEAN_OR_NULL_FIRST_CHARS = Object.keys(BOOLEAN_OR_NULL).map((k) => k[0]);\nconst BOOLEAN_OR_NULL_CHARS = [...new Set(Object.keys(BOOLEAN_OR_NULL).flatMap((k) => [...k]))];\n\nexport type JsonParserOptions = {\n\t/** If true, the stream is allowed to contain multiple JSON values on the root level */\n\tmulti?: boolean;\n}\n\n/**\n * Parses a JSON string stream into a stream of JsonChunks.\n * Unless multi is true, the JSON string must contain only one JSON value (object/array/string/number/boolean/null)\n * on the root level, otherwise the stream will fail with an error.\n */\nexport class JsonParser extends AbstractTransformStream<string, JsonChunk> {\n\tprotected state: State = { type: StateType.START };\n\tprotected lengthBeforeCurrentChunk = 0;\n\n\tconstructor(protected options: JsonParserOptions = {}) {\n\t\tsuper();\n\t}\n\n\t/**\n\t * Checks whether a token that doesn't have an explicit end character (that is: numbers and whitespaces) has ended, and if\n\t * so, update the state and emit the appropriate chunks.\n\t * @param char The next character on the stream. Is used to check whether the current token ends (for example, a number is ended\n\t * by a non-number character). If undefined, the stream is assumed to have ended, so the current token must always end.\n\t */\n\tprotected checkValueEnd(controller: TransformStreamDefaultController<JsonChunk>, char: string | undefined): void {\n\t\tif (this.state.type === StateType.WHITESPACE && (char == null || !WHITESPACE_CHARS.includes(char))) {\n\t\t\tif (this.state.rawValue.length > 0) {\n\t\t\t\tcontroller.enqueue(whitespace(this.state.rawValue));\n\t\t\t}\n\t\t\tthis.state = this.state.parentState;\n\t\t}\n\n\t\tif (\n\t\t\t(this.state.type === StateType.NUMBER_DIGITS && (char == null || ![...NUMBER_CHARS, \".\", \"e\", \"E\"].includes(char)))\n\t\t\t|| (this.state.type === StateType.NUMBER_DECIMAL_DIGITS && (char == null || ![...NUMBER_CHARS, \"e\", \"E\"].includes(char)))\n\t\t\t|| (this.state.type === StateType.NUMBER_E_DIGITS && (char == null || !NUMBER_CHARS.includes(char)))\n\t\t) {\n\t\t\tcontroller.enqueue(numberValue(Number(this.state.rawValue), this.state.rawValue));\n\t\t\tthis.state = getStateAfterValue(this.state.parentState);\n\t\t}\n\t}\n\n\t/**\n\t * Handle a single character piped into the stream.\n\t */\n\tprotected handleChar(controller: TransformStreamDefaultController<JsonChunk>, context: Context): void {\n\t\tconst char = context.char;\n\n\t\t// End chunks that don't have an explicit end char\n\t\tthis.checkValueEnd(controller, char);\n\n\n\t\t// Objects\n\n\t\tif (char === \"{\" && isState(this.state, this.options.multi ? VALUE_START_ALLOWED_MULTI : VALUE_START_ALLOWED)) {\n\t\t\tcontroller.enqueue(objectStart(char));\n\t\t\tthis.state = {\n\t\t\t\ttype: StateType.OBJECT_AFTER_START,\n\t\t\t\tparentState: this.state\n\t\t\t};\n\t\t\treturn;\n\t\t}\n\n\t\tif (char === \"}\" && isState(this.state, [StateType.OBJECT_AFTER_START, StateType.OBJECT_AFTER_VALUE])) {\n\t\t\tcontroller.enqueue(objectEnd(char));\n\t\t\tthis.state = getStateAfterValue(this.state.parentState);\n\t\t\treturn;\n\t\t}\n\n\t\tif (char === \":\" && isState(this.state, [StateType.OBJECT_AFTER_KEY])) {\n\t\t\tcontroller.enqueue(colon(char));\n\t\t\tthis.state = {\n\t\t\t\ttype: StateType.OBJECT_AFTER_COLON,\n\t\t\t\tparentState: this.state.parentState\n\t\t\t};\n\t\t\treturn;\n\t\t}\n\n\t\tif (char === \",\" && isState(this.state, [StateType.OBJECT_AFTER_VALUE])) {\n\t\t\tcontroller.enqueue(comma(char));\n\t\t\tthis.state = {\n\t\t\t\ttype: StateType.OBJECT_AFTER_COMMA,\n\t\t\t\tparentState: this.state.parentState\n\t\t\t};\n\t\t\treturn;\n\t\t}\n\n\n\t\t// Arrays\n\n\t\tif (char === \"[\" && isState(this.state, this.options.multi ? VALUE_START_ALLOWED_MULTI : VALUE_START_ALLOWED)) {\n\t\t\tcontroller.enqueue(arrayStart(char));\n\t\t\tthis.state = {\n\t\t\t\ttype: StateType.ARRAY_AFTER_START,\n\t\t\t\tparentState: this.state\n\t\t\t};\n\t\t\treturn;\n\t\t}\n\n\t\tif (char === \"]\" && isState(this.state, [StateType.ARRAY_AFTER_START, StateType.ARRAY_AFTER_VALUE])) {\n\t\t\tcontroller.enqueue(arrayEnd(char));\n\t\t\tthis.state = getStateAfterValue(this.state.parentState);\n\t\t\treturn;\n\t\t}\n\n\t\tif (char === \",\" && isState(this.state, [StateType.ARRAY_AFTER_VALUE])) {\n\t\t\tcontroller.enqueue(comma(char));\n\t\t\tthis.state = {\n\t\t\t\ttype: StateType.ARRAY_AFTER_COMMA,\n\t\t\t\tparentState: this.state.parentState\n\t\t\t};\n\t\t\treturn;\n\t\t}\n\n\n\t\t// Boolean/null\n\n\t\tif (BOOLEAN_OR_NULL_FIRST_CHARS.includes(char) && isState(this.state, this.options.multi ? VALUE_START_ALLOWED_MULTI : VALUE_START_ALLOWED)) {\n\t\t\tthis.state = {\n\t\t\t\ttype: StateType.BOOLEAN_OR_NULL,\n\t\t\t\trawValue: char,\n\t\t\t\tparentState: this.state\n\t\t\t};\n\t\t\treturn;\n\t\t}\n\n\t\tif (BOOLEAN_OR_NULL_CHARS.includes(char) && this.state.type === StateType.BOOLEAN_OR_NULL) {\n\t\t\tconst rawValue = `${this.state.rawValue}${char}`;\n\t\t\tfor (const [key, value] of Object.entries(BOOLEAN_OR_NULL)) {\n\t\t\t\tif (rawValue === key) {\n\t\t\t\t\tif (typeof value === \"boolean\") {\n\t\t\t\t\t\tcontroller.enqueue(booleanValue(value, rawValue));\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcontroller.enqueue(nullValue(rawValue));\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.state = getStateAfterValue(this.state.parentState);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (key.startsWith(rawValue)) {\n\t\t\t\t\tthis.state.rawValue = rawValue;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\n\t\t// Strings\n\n\t\tif (char === \"\\\"\") {\n\t\t\tif (isState(this.state, this.options.multi ? VALUE_START_ALLOWED_MULTI : VALUE_START_ALLOWED)) {\n\t\t\t\tcontroller.enqueue(stringStart(StringRole.VALUE, char));\n\t\t\t\tthis.state = {\n\t\t\t\t\ttype: StateType.STRING,\n\t\t\t\t\tvalue: \"\",\n\t\t\t\t\trawValue: \"\",\n\t\t\t\t\trole: StringRole.VALUE,\n\t\t\t\t\tparentState: this.state\n\t\t\t\t};\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (isState(this.state, KEY_START_ALLOWED)) {\n\t\t\t\tcontroller.enqueue(stringStart(StringRole.KEY, char));\n\t\t\t\tthis.state = {\n\t\t\t\t\ttype: StateType.STRING,\n\t\t\t\t\tvalue: \"\",\n\t\t\t\t\trawValue: \"\",\n\t\t\t\t\trole: StringRole.KEY,\n\t\t\t\t\tparentState: this.state\n\t\t\t\t};\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (isState(this.state, [StateType.STRING])) {\n\t\t\t\tif (this.state.rawValue.length > 0) {\n\t\t\t\t\tcontroller.enqueue(stringChunk(this.state.value, this.state.role, this.state.rawValue));\n\t\t\t\t}\n\n\t\t\t\tcontroller.enqueue(stringEnd(this.state.role, char));\n\t\t\t\tthis.state = getStateAfterValue(this.state.parentState);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tif (char === \"\\\\\" && isState(this.state, [StateType.STRING])) {\n\t\t\tthis.state = {\n\t\t\t\ttype: StateType.STRING_AFTER_BACKSLASH,\n\t\t\t\trawValue: char,\n\t\t\t\tparentState: this.state\n\t\t\t};\n\t\t\treturn;\n\t\t}\n\n\t\tif (Object.prototype.hasOwnProperty.call(STRING_ESCAPE_CHARS, char) && isState(this.state, [StateType.STRING_AFTER_BACKSLASH])) {\n\t\t\tthis.state = {\n\t\t\t\t...this.state.parentState,\n\t\t\t\tvalue: `${this.state.parentState.value}${STRING_ESCAPE_CHARS[char as keyof typeof STRING_ESCAPE_CHARS]}`,\n\t\t\t\trawValue: `${this.state.parentState.rawValue}${this.state.rawValue}${char}`\n\t\t\t};\n\t\t\treturn;\n\t\t}\n\n\t\tif (char === \"u\" && isState(this.state, [StateType.STRING_AFTER_BACKSLASH])) {\n\t\t\tthis.state = {\n\t\t\t\ttype: StateType.STRING_AFTER_BACKSLASH_U,\n\t\t\t\tvalue: \"\",\n\t\t\t\trawValue: `${this.state.rawValue}${char}`,\n\t\t\t\tparentState: this.state.parentState\n\t\t\t};\n\t\t\treturn;\n\t\t}\n\n\t\tif (HEX_NUMBER_CHARS.includes(char) && isState(this.state, [StateType.STRING_AFTER_BACKSLASH_U])) {\n\t\t\tthis.state.value += char;\n\t\t\tthis.state.rawValue += char;\n\t\t\tif (this.state.value.length === 4) {\n\t\t\t\tthis.state = {\n\t\t\t\t\t...this.state.parentState,\n\t\t\t\t\tvalue: `${this.state.parentState.value}${String.fromCharCode(parseInt(this.state.value, 16))}`,\n\t\t\t\t\trawValue: `${this.state.parentState.rawValue}${this.state.rawValue}`\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (char.charCodeAt(0) >= 0x20 && isState(this.state, [StateType.STRING])) {\n\t\t\tthis.state.value += char;\n\t\t\tthis.state.rawValue += char;\n\t\t\treturn;\n\t\t}\n\n\n\t\t// Numbers\n\n\t\tif (char === \"-\" && isState(this.state, this.options.multi ? VALUE_START_ALLOWED_MULTI : VALUE_START_ALLOWED)) {\n\t\t\tthis.state = {\n\t\t\t\ttype: StateType.NUMBER_MINUS,\n\t\t\t\trawValue: char,\n\t\t\t\tparentState: this.state\n\t\t\t};\n\t\t\treturn;\n\t\t}\n\n\t\tif ((char === \"-\" || char === \"+\") && this.state.type === StateType.NUMBER_E) {\n\t\t\tthis.state = {\n\t\t\t\ttype: StateType.NUMBER_E_PLUSMINUS,\n\t\t\t\trawValue: `${this.state.rawValue}${char}`,\n\t\t\t\tparentState: this.state.parentState\n\t\t\t};\n\t\t\treturn;\n\t\t}\n\n\t\tif (char === \".\" && this.state.type === StateType.NUMBER_DIGITS) {\n\t\t\tthis.state = {\n\t\t\t\ttype: StateType.NUMBER_POINT,\n\t\t\t\trawValue: `${this.state.rawValue}${char}`,\n\t\t\t\tparentState: this.state.parentState\n\t\t\t};\n\t\t\treturn;\n\t\t}\n\n\t\tif ((char === \"e\" || char === \"E\") && isState(this.state, [StateType.NUMBER_DIGITS, StateType.NUMBER_DECIMAL_DIGITS])) {\n\t\t\tthis.state = {\n\t\t\t\ttype: StateType.NUMBER_E,\n\t\t\t\trawValue: `${this.state.rawValue}${char}`,\n\t\t\t\tparentState: this.state.parentState\n\t\t\t};\n\t\t\treturn;\n\t\t}\n\n\t\tif (NUMBER_CHARS.includes(char)) {\n\t\t\tif (this.state.type === StateType.NUMBER_MINUS) {\n\t\t\t\tthis.state = {\n\t\t\t\t\ttype: StateType.NUMBER_DIGITS,\n\t\t\t\t\trawValue: `${this.state.rawValue}${char}`,\n\t\t\t\t\tparentState: this.state.parentState\n\t\t\t\t};\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (this.state.type === StateType.NUMBER_POINT) {\n\t\t\t\tthis.state = {\n\t\t\t\t\ttype: StateType.NUMBER_DECIMAL_DIGITS,\n\t\t\t\t\trawValue: `${this.state.rawValue}${char}`,\n\t\t\t\t\tparentState: this.state.parentState\n\t\t\t\t};\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (isState(this.state, [StateType.NUMBER_E, StateType.NUMBER_E_PLUSMINUS])) {\n\t\t\t\tthis.state = {\n\t\t\t\t\ttype: StateType.NUMBER_E_DIGITS,\n\t\t\t\t\trawValue: `${this.state.rawValue}${char}`,\n\t\t\t\t\tparentState: this.state.parentState\n\t\t\t\t};\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (isState(this.state, [StateType.NUMBER_DIGITS, StateType.NUMBER_DECIMAL_DIGITS, StateType.NUMBER_E_DIGITS])) {\n\t\t\t\tthis.state.rawValue += char;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (isState(this.state, this.options.multi ? VALUE_START_ALLOWED_MULTI : VALUE_START_ALLOWED)) {\n\t\t\t\tthis.state = {\n\t\t\t\t\ttype: StateType.NUMBER_DIGITS,\n\t\t\t\t\trawValue: char,\n\t\t\t\t\tparentState: this.state\n\t\t\t\t};\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\n\t\t// Whitespaces\n\n\t\tif (WHITESPACE_CHARS.includes(char) || (this.options.multi && isState(this.state, [StateType.START, StateType.END]) && RS_CHARS.includes(char))) {\n\t\t\tif (this.state.type === StateType.WHITESPACE) {\n\t\t\t\tthis.state.rawValue += char;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (isState(this.state, WHITESPACE_ALLOWED)) {\n\t\t\t\tthis.state = {\n\t\t\t\t\ttype: StateType.WHITESPACE,\n\t\t\t\t\trawValue: char,\n\t\t\t\t\tparentState: this.state\n\t\t\t\t};\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\n\t\tthrow new UnexpectedCharError(context);\n\n\t}\n\n\t/**\n\t * Called at the end of the transformation of a chunk. Should flush partial values where applicable,\n\t * in particular and incomplete strings or whitespaces can be emitted.\n\t */\n\tprotected handleChunkEnd(controller: TransformStreamDefaultController<JsonChunk>): void {\n\t\tconst stringState = (\n\t\t\tthis.state.type === StateType.STRING ? this.state :\n\t\t\tisState(this.state, [StateType.STRING_AFTER_BACKSLASH, StateType.STRING_AFTER_BACKSLASH_U]) ? this.state.parentState :\n\t\t\tundefined\n\t\t);\n\t\tif (stringState) {\n\t\t\tcontroller.enqueue(stringChunk(stringState.value, stringState.role, stringState.rawValue));\n\t\t\tstringState.rawValue = \"\";\n\t\t\tstringState.value = \"\";\n\t\t}\n\n\t\tif (this.state.type === StateType.WHITESPACE && this.state.rawValue.length > 0) {\n\t\t\tcontroller.enqueue(whitespace(this.state.rawValue));\n\t\t\tthis.state.rawValue = \"\";\n\t\t}\n\t}\n\n\t/**\n\t * Transforms an incoming chunk.\n\t */\n\tprotected override transform(chunk: string, controller: TransformStreamDefaultController<JsonChunk>): void {\n\t\tfor (let i = 0; i < chunk.length; i++) {\n\t\t\tthis.handleChar(controller, { char: chunk[i], position: this.lengthBeforeCurrentChunk + i });\n\t\t}\n\t\tthis.lengthBeforeCurrentChunk += chunk.length;\n\n\t\tthis.handleChunkEnd(controller);\n\t}\n\n\t/**\n\t * Called when the end of the incoming stream is reached. Checks that a complete value has been emitted.\n\t */\n\tprotected override flush(controller: TransformStreamDefaultController<JsonChunk>): void {\n\t\tthis.checkValueEnd(controller, undefined);\n\n\t\tif (this.state.type !== StateType.END && (!this.options.multi || this.state.type !== StateType.START)) {\n\t\t\tthrow new PrematureEndError();\n\t\t}\n\n\t\tcontroller.terminate();\n\t}\n}","import { JsonParser } from \"./json-parser\";\nimport { StringRole, arrayEnd, arrayStart, booleanValue, colon, comma, nullValue, numberValue, objectEnd, objectStart, stringChunk, stringEnd, stringStart, whitespace, type JsonChunk } from \"./types\";\nimport { AbstractTransformStream, iterableToStream, streamToIterable, stringToStream } from \"./utils\";\n\ntype AnyIterable<T> = Iterable<T> | AsyncIterable<T> | ReadableStream<T>;\n\nfunction normalizeStream<T, S extends symbol>(iterable: AnyIterable<T>, symbol: S): ReadableStream<T> & { [K in S]: true } {\n\tif (Symbol.asyncIterator in iterable || Symbol.iterator in iterable) {\n\t\treturn Object.assign(iterableToStream(iterable), { [symbol]: true as const });\n\t} else {\n\t\treturn Object.assign(Object.create(iterable), { [symbol]: true as const });\n\t}\n}\n\nconst stringStreamSymbol = Symbol(\"stringStream\");\nexport type StringStream = ReadableStream<string> & { [stringStreamSymbol]: true };\nexport function stringStream(stream: AnyIterable<string>): StringStream {\n\treturn normalizeStream(stream, stringStreamSymbol);\n}\nexport function isStringStream(value: any): value is StringStream {\n\treturn value && typeof value === \"object\" && !!value[stringStreamSymbol];\n}\n\nconst objectStreamSymbol = Symbol(\"objectStream\");\nexport type ObjectStream<V> = ReadableStream<[key: string | StringStream, value: V]> & { [objectStreamSymbol]: true };\nexport function objectStream<T>(obj: AnyIterable<[key: string | StringStream, value: T]>): ObjectStream<T> {\n\treturn normalizeStream(obj, objectStreamSymbol);\n}\nexport function isObjectStream(value: any): value is ObjectStream<any> {\n\treturn value && typeof value === \"object\" && !!value[objectStreamSymbol];\n}\n\nconst arrayStreamSymbol = Symbol(\"arrayStream\");\nexport type ArrayStream<V> = ReadableStream<V> & { [arrayStreamSymbol]: true };\nexport function arrayStream<T>(obj: AnyIterable<T>): ArrayStream<T> {\n\treturn normalizeStream(obj, arrayStreamSymbol);\n}\nexport function isArrayStream(value: any): value is ArrayStream<any> {\n\treturn value && typeof value === \"object\" && !!value[arrayStreamSymbol];\n}\n\n\ntype SyncOrAsync<T> = T | Promise<T> | (() => T | Promise<T>);\nexport type SerializableJsonValue = SyncOrAsync<\n\t| { [key: string | number]: SerializableJsonValue }\n\t| (ReadableStream<[key: string | StringStream, value: SerializableJsonValue]> & { [objectStreamSymbol]: true }) // Cannot use ObjectStream<StreamedJsonValue> due to circular reference\n\t| Array<SerializableJsonValue>\n\t| ReadableStream<SerializableJsonValue> & { [arrayStreamSymbol]: true } // Cannot use ArrayStream<StreamedJsonValue> due to circular reference\n\t| string\n\t| StringStream\n\t| number | boolean | null\n\t| undefined\n>;\n\nfunction normalizeSpace(space: string | number | undefined): string {\n\tif (typeof space === \"number\") {\n\t\treturn \" \".repeat(space);\n\t} else if (typeof space === \"string\") {\n\t\treturn space;\n\t} else {\n\t\treturn \"\";\n\t}\n}\n\nasync function* serializeJson(value: SerializableJsonValue, space?: string | number, spacePrefix = \"\", key = \"\"): AsyncIterable<JsonChunk> {\n\tconst normalizedSpace = normalizeSpace(space);\n\tlet val = await (typeof value === \"function\" && !(\"toJSON\" in value) ? value() : value);\n\tval = (val && \"toJSON\" in Object(val)) ? Object(val).toJSON(key) : val;\n\n\tif (typeof val === \"boolean\" || (typeof val === \"object\" && Object.prototype.toString.call(val) === \"[object Boolean]\")) {\n\t\tyield booleanValue(Boolean(val));\n\t} else if (typeof val === \"number\" || (typeof val === \"object\" && Object.prototype.toString.call(val) === \"[object Number]\")) {\n\t\tconst num = Number(val);\n\t\tif (isFinite(num)) {\n\t\t\tyield numberValue(num);\n\t\t} else {\n\t\t\tyield nullValue();\n\t\t}\n\t} else if (typeof val === \"bigint\" || (typeof val === \"object\" && Object.prototype.toString.call(val) === \"[object BigInt]\")) {\n\t\tyield numberValue(Number(val), String(val));\n\t} else if (typeof val === \"string\" || (typeof val === \"object\" && Object.prototype.toString.call(val) === \"[object String]\") || isStringStream(val)) {\n\t\tyield stringStart();\n\t\tfor await (const chunk of isStringStream(val) ? streamToIterable(val) : [String(val)]) {\n\t\t\tyield stringChunk(chunk);\n\t\t}\n\t\tyield stringEnd();\n\t} else if (\"isRawJSON\" in JSON && (JSON as any).isRawJSON(val)) {\n\t\tfor await (const chunk of streamToIterable(stringToStream((val as any).rawJSON).pipeThrough(new JsonParser()))) {\n\t\t\tyield chunk;\n\t\t}\n\t} else if (Array.isArray(val) || isArrayStream(val)) {\n\t\tyield arrayStart();\n\n\t\tlet i = 0;\n\t\tfor await (const v of isArrayStream(val) ? streamToIterable(val) : val) {\n\t\t\tif (i > 0) {\n\t\t\t\tyield comma();\n\t\t\t}\n\n\t\t\tif (normalizedSpace) {\n\t\t\t\tyield whitespace(`\\n${spacePrefix}${normalizedSpace}`);\n\t\t\t}\n\n\t\t\tfor await (const chunk of serializeJson(v, space, `${spacePrefix}${normalizedSpace}`, `${i}`)) {\n\t\t\t\tyield chunk;\n\t\t\t}\n\n\t\t\ti++;\n\t\t}\n\n\t\tif (i > 0 && normalizedSpace) {\n\t\t\tyield whitespace(`\\n${spacePrefix}`);\n\t\t}\n\n\t\tyield arrayEnd();\n\t} else if (typeof val === \"object\" && val) {\n\t\tyield objectStart();\n\n\t\tlet first = true;\n\t\tfor await (const [k, rawV] of isObjectStream(val) ? streamToIterable(val) : Object.entries(val)) {\n\t\t\tconst v = await (typeof rawV === \"function\" ? rawV() : rawV);\n\t\t\tif (v === undefined || typeof k === \"symbol\" || typeof v === \"symbol\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (first) {\n\t\t\t\tfirst = false;\n\t\t\t} else {\n\t\t\t\tyield comma();\n\t\t\t}\n\n\t\t\tif (normalizedSpace) {\n\t\t\t\tyield whitespace(`\\n${spacePrefix}${normalizedSpace}`);\n\t\t\t}\n\n\t\t\tyield stringStart(StringRole.KEY);\n\t\t\tfor await (const chunk of isStringStream(k) ? streamToIterable(k) : [`${k}`]) {\n\t\t\t\tyield stringChunk(chunk, StringRole.KEY);\n\t\t\t}\n\t\t\tyield stringEnd(StringRole.KEY);\n\t\t\tyield colon();\n\n\t\t\tif (normalizedSpace) {\n\t\t\t\tyield whitespace(\" \");\n\t\t\t}\n\n\t\t\tfor await (const chunk of serializeJson(v, space, `${spacePrefix}${normalizedSpace}`, isStringStream(k) ? \"\" : k)) {\n\t\t\t\tyield chunk;\n\t\t\t}\n\t\t}\n\n\t\tif (!first && normalizedSpace) {\n\t\t\tyield whitespace(`\\n${spacePrefix}`);\n\t\t}\n\n\t\tyield objectEnd();\n\t} else {\n\t\tyield nullValue();\n\t}\n}\n\nexport type JsonSerializerOptions = {\n\t/** White space characters to insert before the first emitted root value. Not emitted if no values are emitted. */\n\tbeforeFirst?: string;\n\t/** White space characters to insert before each but the first emitted root value. Defaults to a newline (\\n). */\n\tdelimiter?: string;\n\t/** White space characters to insert after the last emitted root value. Not emitted if no values are emitted. */\n\tafterLast?: string;\n};\n\n/**\n * Converts any JSON-stringifiable JavaScript values into a stream of JsonChunks.\n */\nexport class JsonSerializer extends AbstractTransformStream<SerializableJsonValue, JsonChunk> {\n\tprotected first = true;\n\n\tconstructor(protected space?: string | number, protected options?: JsonSerializerOptions) {\n\t\tsuper();\n\t}\n\n\toverride async transform(value: SerializableJsonValue, controller: TransformStreamDefaultController<JsonChunk>): Promise<void> {\n\t\tif (this.first) {\n\t\t\tif (this.options?.beforeFirst) {\n\t\t\t\tcontroller.enqueue(whitespace(this.options.beforeFirst));\n\t\t\t}\n\t\t\tthis.first = false;\n\t\t} else if (this.options?.delimiter !== \"\") {\n\t\t\tcontroller.enqueue(whitespace(this.options?.delimiter ?? \"\\n\"));\n\t\t}\n\n\t\tfor await (const chunk of serializeJson(value, this.space)) {\n\t\t\tcontroller.enqueue(chunk);\n\t\t}\n\t}\n\n\tprotected override flush(controller: TransformStreamDefaultController<JsonChunk>): void | Promise<void> {\n\t\tif (!this.first && this.options?.afterLast) {\n\t\t\tcontroller.enqueue(whitespace(this.options.afterLast));\n\t\t}\n\n\t\tcontroller.terminate();\n\t}\n}\n/**\n * Converts any single JSON-stringifiable JavaScript value into a stream of JsonChunks.\n */\nexport function serializeJsonValue(value: SerializableJsonValue, space?: string | number): ReadableStream<JsonChunk> {\n\tconst serializer = new JsonSerializer(space);\n\tconst writer = serializer.writable.getWriter();\n\twriter.write(value).catch(() => undefined);\n\twriter.close().catch(() => undefined);\n\treturn serializer.readable;\n}","import type { JsonChunk } from \"./types\";\nimport { AbstractTransformStream } from \"./utils\";\n\n/**\n * Converts a stream of JsonChunks into a JSON string stream.\n */\nexport class JsonStringifier extends AbstractTransformStream<JsonChunk | { rawValue: string }, string> {\n\tconstructor() {\n\t\tsuper();\n\t}\n\n\tprotected override transform(chunk: JsonChunk | { rawValue: string }, controller: TransformStreamDefaultController<string>): void {\n\t\tcontroller.enqueue(chunk.rawValue);\n\t}\n\n\tprotected override flush(controller: TransformStreamDefaultController<string>): void {\n\t\tcontroller.terminate();\n\t}\n}","import { JsonChunkType, StringRole, type JsonChunk } from \"./types\";\nimport { AbstractTransformStream } from \"./utils\";\n\n/**\n * An array that describes the chain of object and array keys that the current value is located under.\n * String values represent object keys, while numbers represent array indexes (starting at 0).\n * The root value’s path is an empty array.\n *\n * For objects, any chunks between the colon and the comma (or end of object) will have the property\n * key in the path, while other chunks (such as the object start/end and the key string start/chunk/end)\n * will have the path of the object itself.\n * For arrays, any chunks in between the array start and end except the comma will have the array index\n * in the path, while other chunks (such as the array start/end and the comma) will have the path of\n * the array itself.\n */\nexport type JsonPath = Array<string | number>;\n\nexport type JsonChunkWithPath = JsonChunk & {\n\tpath: JsonPath;\n};\n\n/**\n * Adds a \"path\" property to all JsonChunks passed through it that indicates the path of object property keys\n * and array item indexes where the chunk is located.\n */\nexport class JsonPathDetector extends AbstractTransformStream<JsonChunk, JsonChunkWithPath> {\n\tprotected stack: Array<{\n\t\ttype: \"object\";\n\t\t/** pending: still receiving key STRING_CHUNKs; next: next chunk will transition state to active; active: path applies to all current chunks */\n\t\tstate: \"pending\" | \"next\" | \"active\";\n\t\tkey: string;\n\t} | {\n\t\ttype: \"array\";\n\t\t/** next: next chunk will transition state to active; active: path applies to current chunks */\n\t\tstate: \"next\" | \"active\";\n\t\tkey: number;\n\t}> = [];\n\tprotected path: Array<string | number> = [];\n\n\tconstructor() {\n\t\tsuper();\n\t}\n\n\tprotected override transform(chunk: JsonChunk, controller: TransformStreamDefaultController<JsonChunkWithPath>): void {\n\t\tif (this.stack[this.stack.length - 1]?.state === \"next\") {\n\t\t\tthis.stack[this.stack.length - 1].state = \"active\";\n\t\t\tthis.path.push(this.stack[this.stack.length - 1].key);\n\t\t}\n\n\t\tif (chunk.type === JsonChunkType.OBJECT_START) {\n\t\t\tthis.stack.push({ type: \"object\", state: \"pending\", key: \"\" });\n\t\t} else if (chunk.type === JsonChunkType.ARRAY_START) {\n\t\t\tthis.stack.push({ type: \"array\", state: \"next\", key: 0 });\n\t\t} else if (chunk.type === JsonChunkType.OBJECT_END || chunk.type === JsonChunkType.ARRAY_END) {\n\t\t\tif (this.stack.pop()?.state !== \"pending\") {\n\t\t\t\tthis.path.pop();\n\t\t\t}\n\t\t} else {\n\t\t\tconst current = this.stack[this.stack.length - 1];\n\t\t\tif (current?.type === \"object\") {\n\t\t\t\tif (chunk.type === JsonChunkType.STRING_CHUNK && chunk.role === StringRole.KEY) {\n\t\t\t\t\tcurrent.key += chunk.value;\n\t\t\t\t} else if (chunk.type === JsonChunkType.COLON) {\n\t\t\t\t\tcurrent.state = \"next\";\n\t\t\t\t} else if (chunk.type === JsonChunkType.COMMA) {\n\t\t\t\t\tthis.path.pop();\n\t\t\t\t\tcurrent.state = \"pending\";\n\t\t\t\t\tcurrent.key = \"\";\n\t\t\t\t}\n\t\t\t} else if (current?.type === \"array\") {\n\t\t\t\tif (chunk.type === JsonChunkType.COMMA) {\n\t\t\t\t\tcurrent.state = \"next\";\n\t\t\t\t\tcurrent.key++;\n\t\t\t\t\tthis.path.pop();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tcontroller.enqueue({ ...chunk, path: [...this.path] });\n\t}\n}","import type { JsonChunkWithPath, JsonPath } from \"./json-path-detector\";\nimport { AbstractTransformStream, arrayStartsWith } from \"./utils\";\n\n/**\n * A selector that can be set for a Path