tupleson
Version:
A hackable JSON serializer/deserializer
1 lines • 14.7 kB
Source Map (JSON)
{"version":3,"sources":["../../src/async/deserializeAsync.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unsafe-assignment */\n/* eslint-disable @typescript-eslint/no-non-null-assertion */\n\nimport { TsonError } from \"../errors.js\";\nimport { assert } from \"../internals/assert.js\";\nimport { isTsonTuple } from \"../internals/isTsonTuple.js\";\nimport { mapOrReturn } from \"../internals/mapOrReturn.js\";\nimport {\n\tTsonNonce,\n\tTsonSerialized,\n\tTsonTransformerSerializeDeserialize,\n} from \"../sync/syncTypes.js\";\nimport { TsonAbortError, TsonStreamInterruptedError } from \"./asyncErrors.js\";\nimport {\n\tTsonAsyncIndex,\n\tTsonAsyncOptions,\n\tTsonAsyncStringifierIterable,\n\tTsonAsyncType,\n} from \"./asyncTypes.js\";\nimport {\n\tcreateReadableStream,\n\tmapIterable,\n\treadableStreamToAsyncIterable,\n} from \"./iterableUtils.js\";\nimport { TsonAsyncValueTuple } from \"./serializeAsync.js\";\n\ntype WalkFn = (value: unknown) => unknown;\ntype WalkerFactory = (nonce: TsonNonce) => WalkFn;\n\ntype AnyTsonTransformerSerializeDeserialize =\n\t| TsonAsyncType<any, any>\n\t| TsonTransformerSerializeDeserialize<any, any>;\n\nexport interface TsonParseAsyncOptions {\n\t/**\n\t * Event handler for when the stream reconnects\n\t * You can use this to do extra actions to ensure no messages were lost\n\t */\n\tonReconnect?: () => void;\n\t/**\n\t * On stream error\n\t */\n\tonStreamError?: (err: TsonStreamInterruptedError) => void;\n\t/**\n\t * Allow reconnecting to the stream if it's interrupted\n\t * @default false\n\t */\n\treconnect?: boolean;\n}\n\ntype TsonParseAsync = <TValue>(\n\tstring: AsyncIterable<string> | TsonAsyncStringifierIterable<TValue>,\n\topts?: TsonParseAsyncOptions,\n) => Promise<TValue>;\n\ntype TsonDeserializeIterableValue = TsonAsyncValueTuple | TsonSerialized;\ntype TsonDeserializeIterable = AsyncIterable<TsonDeserializeIterableValue>;\nfunction createTsonDeserializer(opts: TsonAsyncOptions) {\n\tconst typeByKey: Record<string, AnyTsonTransformerSerializeDeserialize> = {};\n\n\tfor (const handler of opts.types) {\n\t\tif (handler.key) {\n\t\t\tif (typeByKey[handler.key]) {\n\t\t\t\tthrow new Error(`Multiple handlers for key ${handler.key} found`);\n\t\t\t}\n\n\t\t\ttypeByKey[handler.key] =\n\t\t\t\thandler as AnyTsonTransformerSerializeDeserialize;\n\t\t}\n\t}\n\n\treturn async (\n\t\titerable: TsonDeserializeIterable,\n\t\tparseOptions: TsonParseAsyncOptions,\n\t) => {\n\t\tconst controllers = new Map<\n\t\t\tTsonAsyncIndex,\n\t\t\tReadableStreamDefaultController<unknown>\n\t\t>();\n\t\tconst cache = new Map<TsonAsyncIndex, unknown>();\n\t\tconst iterator = iterable[Symbol.asyncIterator]();\n\n\t\tconst walker: WalkerFactory = (nonce) => {\n\t\t\tconst walk: WalkFn = (value) => {\n\t\t\t\tif (isTsonTuple(value, nonce)) {\n\t\t\t\t\tconst [type, serializedValue] = value;\n\t\t\t\t\tconst transformer = typeByKey[type];\n\n\t\t\t\t\tassert(transformer, `No transformer found for type ${type}`);\n\n\t\t\t\t\tif (!transformer.async) {\n\t\t\t\t\t\tconst walkedValue = walk(serializedValue);\n\t\t\t\t\t\treturn transformer.deserialize(walkedValue);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst idx = serializedValue as TsonAsyncIndex;\n\n\t\t\t\t\tif (cache.has(idx)) {\n\t\t\t\t\t\t// We already have this async value in the cache - so this is probably a reconnect\n\t\t\t\t\t\tassert(\n\t\t\t\t\t\t\tparseOptions.reconnect,\n\t\t\t\t\t\t\t\"Duplicate index found but reconnect is off\",\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn cache.get(idx);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst [readable, controller] = createReadableStream();\n\n\t\t\t\t\t// the `start` method is called \"immediately when the object is constructed\"\n\t\t\t\t\t// [MDN](http://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/ReadableStream)\n\t\t\t\t\t// so we're guaranteed that the controller is set in the cache\n\t\t\t\t\tassert(controller, \"Controller not set - this is a bug\");\n\n\t\t\t\t\tcontrollers.set(idx, controller);\n\n\t\t\t\t\tconst result = transformer.deserialize({\n\t\t\t\t\t\tclose() {\n\t\t\t\t\t\t\tcontroller.close();\n\t\t\t\t\t\t\tcontrollers.delete(idx);\n\t\t\t\t\t\t},\n\t\t\t\t\t\treader: readable.getReader(),\n\t\t\t\t\t});\n\n\t\t\t\t\tcache.set(idx, result);\n\t\t\t\t\treturn result;\n\t\t\t\t}\n\n\t\t\t\treturn mapOrReturn(value, walk);\n\t\t\t};\n\n\t\t\treturn walk;\n\t\t};\n\n\t\tasync function getStreamedValues(walk: WalkFn) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n\t\t\twhile (true) {\n\t\t\t\tconst nextValue = await iterator.next();\n\t\t\t\tif (nextValue.done) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tconst { value } = nextValue;\n\n\t\t\t\tif (!Array.isArray(value)) {\n\t\t\t\t\t// we got the beginning of a new stream - probably because a reconnect\n\t\t\t\t\t// we assume this new stream will have the same shape and restart the walker with the nonce\n\n\t\t\t\t\tparseOptions.onReconnect?.();\n\n\t\t\t\t\tassert(\n\t\t\t\t\t\tparseOptions.reconnect,\n\t\t\t\t\t\t\"Stream got beginning of results but reconnecting is not enabled\",\n\t\t\t\t\t);\n\n\t\t\t\t\tawait getStreamedValues(walker(value.nonce));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst [index, result] = value as TsonAsyncValueTuple;\n\n\t\t\t\tconst controller = controllers.get(index);\n\n\t\t\t\tconst walkedResult = walk(result);\n\n\t\t\t\tif (!parseOptions.reconnect) {\n\t\t\t\t\tassert(controller, `No stream found for index ${index}`);\n\t\t\t\t}\n\n\t\t\t\t// resolving deferred\n\t\t\t\tcontroller?.enqueue(walkedResult);\n\t\t\t}\n\t\t}\n\n\t\tasync function init() {\n\t\t\t// get the head of the JSON\n\t\t\tconst nextValue = await iterator.next();\n\t\t\tif (nextValue.done) {\n\t\t\t\tthrow new TsonError(\"Unexpected end of stream before head\");\n\t\t\t}\n\n\t\t\tconst head = nextValue.value as TsonSerialized<any>;\n\n\t\t\tconst walk = walker(head.nonce);\n\n\t\t\ttry {\n\t\t\t\tconst walked = walk(head.json);\n\n\t\t\t\treturn walked;\n\t\t\t} finally {\n\t\t\t\tgetStreamedValues(walk).catch((cause) => {\n\t\t\t\t\t// Something went wrong while getting the streamed values\n\n\t\t\t\t\tconst err = new TsonStreamInterruptedError(cause);\n\n\t\t\t\t\t// enqueue the error to all the streams\n\t\t\t\t\tfor (const controller of controllers.values()) {\n\t\t\t\t\t\tcontroller.enqueue(err);\n\t\t\t\t\t}\n\n\t\t\t\t\tparseOptions.onStreamError?.(err);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn await init().catch((cause: unknown) => {\n\t\t\tthrow new TsonStreamInterruptedError(cause);\n\t\t});\n\t};\n}\n\nfunction lineAccumulator() {\n\tlet accumulator = \"\";\n\tconst lines: string[] = [];\n\n\treturn {\n\t\tlines,\n\t\tpush(chunk: string) {\n\t\t\taccumulator += chunk;\n\n\t\t\tconst parts = accumulator.split(\"\\n\");\n\t\t\taccumulator = parts.pop() ?? \"\";\n\t\t\tlines.push(...parts);\n\t\t},\n\t};\n}\n\nasync function* stringIterableToTsonIterable(\n\titerable: AsyncIterable<string>,\n): TsonDeserializeIterable {\n\t// get the head of the JSON\n\tconst acc = lineAccumulator();\n\n\t// state of stream\n\tconst AWAITING_HEAD = 0;\n\tconst STREAMING_VALUES = 1;\n\tconst ENDED = 2;\n\n\tlet state: typeof AWAITING_HEAD | typeof ENDED | typeof STREAMING_VALUES =\n\t\tAWAITING_HEAD;\n\n\t// iterate values & yield them\n\n\tfor await (const str of iterable) {\n\t\tacc.push(str);\n\n\t\tif (state === AWAITING_HEAD && acc.lines.length >= 2) {\n\t\t\t/**\n\t\t\t * First line is just a `[`\n\t\t\t */\n\t\t\tacc.lines.shift();\n\n\t\t\t// Second line is the head\n\t\t\tconst headLine = acc.lines.shift();\n\n\t\t\tassert(headLine, \"No head line found\");\n\n\t\t\tconst head = JSON.parse(headLine) as TsonSerialized<any>;\n\n\t\t\tyield head;\n\n\t\t\tstate = STREAMING_VALUES;\n\t\t}\n\n\t\tif (state === STREAMING_VALUES) {\n\t\t\twhile (acc.lines.length) {\n\t\t\t\tlet str = acc.lines.shift()!;\n\n\t\t\t\t// console.log(\"got str\", str);\n\t\t\t\tstr = str.trimStart();\n\n\t\t\t\tif (str.startsWith(\",\")) {\n\t\t\t\t\t// ignore leading comma\n\t\t\t\t\tstr = str.slice(1);\n\t\t\t\t}\n\n\t\t\t\tif (str === \"\" || str === \"[\" || str === \",\") {\n\t\t\t\t\t// beginning of values array or empty string\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (str === \"]]\") {\n\t\t\t\t\t// end of values and stream\n\t\t\t\t\tstate = ENDED;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tyield JSON.parse(str) as TsonAsyncValueTuple;\n\t\t\t}\n\t\t}\n\t}\n\n\tassert(state === ENDED, `Stream ended unexpectedly (state ${state})`);\n}\n\nexport function createTsonParseAsync(opts: TsonAsyncOptions): TsonParseAsync {\n\tconst instance = createTsonDeserializer(opts);\n\n\treturn (async (iterable, opts) => {\n\t\tconst tsonIterable = stringIterableToTsonIterable(iterable);\n\n\t\treturn await instance(tsonIterable, opts ?? {});\n\t}) as TsonParseAsync;\n}\n\nexport function createTsonParseEventSource(opts: TsonAsyncOptions) {\n\tconst instance = createTsonDeserializer(opts);\n\n\treturn async <TValue = unknown>(\n\t\turl: string,\n\t\tparseOpts: TsonParseAsyncOptions & {\n\t\t\tsignal?: AbortSignal;\n\t\t} = {},\n\t) => {\n\t\tconst [stream, controller] =\n\t\t\tcreateReadableStream<TsonDeserializeIterableValue>();\n\t\tconst eventSource = new EventSource(url);\n\n\t\tconst { signal } = parseOpts;\n\t\tconst onAbort = () => {\n\t\t\tassert(signal);\n\t\t\teventSource.close();\n\t\t\tcontroller.error(new TsonAbortError(\"Stream aborted by user\"));\n\n\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t};\n\n\t\tsignal?.addEventListener(\"abort\", onAbort);\n\n\t\teventSource.onmessage = (msg) => {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n\t\t\tcontroller.enqueue(JSON.parse(msg.data));\n\t\t};\n\n\t\teventSource.addEventListener(\"close\", () => {\n\t\t\tcontroller.close();\n\t\t\teventSource.close();\n\t\t});\n\n\t\tconst iterable = readableStreamToAsyncIterable(stream);\n\t\treturn (await instance(iterable, parseOpts)) as TValue;\n\t};\n}\n\nexport function createTsonParseJsonStreamResponse(opts: TsonAsyncOptions) {\n\tconst instance = createTsonParseAsync(opts);\n\n\tconst textDecoder = opts.textDecoder ?? new TextDecoder();\n\n\treturn async <TValue = unknown>(response: Response) => {\n\t\tassert(response.body, \"Response body is empty\");\n\n\t\tconst stringIterator = mapIterable(\n\t\t\treadableStreamToAsyncIterable(response.body),\n\t\t\t(v) => textDecoder.decode(v),\n\t\t);\n\n\t\tconst output = await instance<TValue>(stringIterator);\n\n\t\treturn output;\n\t};\n}\n"],"mappings":"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","names":["str","opts"]}