UNPKG

@tanstack/router-core

Version:

Modern and scalable routing for React applications

164 lines (149 loc) 4.19 kB
import { createStream } from 'seroval' // Web-only primitives shared by the JSON and SSR RawStream plugins. This // module is part of the client bundle: it must not reference `Buffer`, and // the encode and decode halves must stay independently tree-shakeable. export function toBase64(bytes: Uint8Array) { const chunks: Array<string> = [] for (let i = 0; i < bytes.length; i += 0x8000) { chunks.push( String.fromCharCode.apply(null, bytes.subarray(i, i + 0x8000) as any), ) } return btoa(chunks.join('')) } export function fromBase64(value: string) { const binary = atob(value) const bytes = new Uint8Array(binary.length) for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i) } return bytes } const textDecoder = /* @__PURE__ */ new TextDecoder('utf-8', { fatal: true, ignoreBOM: true, }) /** `'t' + utf8` for a valid UTF-8 chunk, otherwise `'b' + base64`. */ export function encodeText(value: Uint8Array) { try { return 't' + textDecoder.decode(value) } catch { return 'b' + toBase64(value) } } const textEncoder = /* @__PURE__ */ new TextEncoder() export function decodeText(value: string) { const data = value.slice(1) return value[0] === 't' ? textEncoder.encode(data) : fromBase64(data) } /** * Pump a byte stream into a Seroval stream, one encoded chunk per read. * Returns the Seroval stream and a `stop` function that cancels the reader * without signalling the Seroval stream; the abort signal and read failures * stop the pump and throw through the Seroval stream. */ export function pumpEncodedStream( readable: ReadableStream<Uint8Array>, encode: (value: Uint8Array) => string, signal?: AbortSignal, ) { signal?.throwIfAborted() const stream = createStream<string | undefined>() const reader = readable.getReader() let active = true const release = () => { active = false signal?.removeEventListener('abort', abort) reader.releaseLock() } const stop = (reason?: unknown) => { if (!active) { return false } void reader.cancel(reason).catch(() => {}) release() return true } const abort = () => { if (stop(signal!.reason)) { stream.throw(signal!.reason) } } signal?.addEventListener('abort', abort) ;(async () => { try { while (active) { const { done, value } = await reader.read() if (!active) { return } if (done) { release() stream.return(undefined) return } stream.next(encode(value)) } } catch (error) { if (stop(error)) { stream.throw(error) } } })() return [stream, stop] as const } /** Rebuild a byte stream from encoded Seroval stream chunks. */ export function fromEncodedStream( source: ReturnType<typeof createStream<string | undefined>>, decode: (value: string) => Uint8Array, ) { // Dropping the disposer after a terminal event lets Seroval's buffer be // collected while application code still holds the finished stream. let unsubscribe: (() => void) | undefined let done = false return new ReadableStream<Uint8Array>({ start(controller) { const dispose = source.on({ next(value) { if (done) { return } try { controller.enqueue(decode(value!)) } catch (error) { // A malformed chunk fails this stream and abandons the source. done = true const stop = unsubscribe unsubscribe = undefined stop?.() controller.error(error) } }, throw(error) { if (!done) { done = true unsubscribe = undefined controller.error(error) } }, return() { if (!done) { done = true unsubscribe = undefined controller.close() } }, }) if (done) { dispose() } else { unsubscribe = dispose } }, cancel() { const dispose = unsubscribe unsubscribe = undefined dispose?.() }, }) }