diff --git a/src/async/asyncTypes.ts b/src/async/asyncTypes.ts index 5ed6bb2..830e093 100644 --- a/src/async/asyncTypes.ts +++ b/src/async/asyncTypes.ts @@ -10,6 +10,10 @@ export type TsonAsyncStringifierIterable = AsyncIterable & { [serialized]: TValue; }; +export type BrandSerialized = TType & { + [serialized]: TValue; +}; + export type TsonAsyncStringifier = ( value: TValue, space?: number, diff --git a/src/async/serializeAsync.ts b/src/async/serializeAsync.ts index 7bab69e..c80d7cf 100644 --- a/src/async/serializeAsync.ts +++ b/src/async/serializeAsync.ts @@ -13,6 +13,7 @@ import { TsonTypeTesterPrimitive, } from "../sync/syncTypes.js"; import { + BrandSerialized, TsonAsyncIndex, TsonAsyncOptions, TsonAsyncStringifier, @@ -246,3 +247,40 @@ export function createTsonStreamAsync( return stringifier as TsonAsyncStringifier; } + +export function crateTsonSSEResponse(opts: TsonAsyncOptions) { + const serialize = createAsyncTsonSerialize(opts); + + return (value: TValue) => { + let controller: ReadableStreamDefaultController = + null as unknown as ReadableStreamDefaultController; + const readable = new ReadableStream({ + start(c) { + controller = c; + }, + }); + + async function iterate() { + const [head, iterable] = serialize(value); + + controller.enqueue(`data: ${JSON.stringify(head)}\n\n`); + for await (const chunk of iterable) { + controller.enqueue(`data: ${JSON.stringify(chunk)}`); + } + } + + iterate().catch((err) => { + controller.error(err); + }); + + const res = new Response(readable, { + headers: { + "Cache-Control": "no-cache", + Connection: "keep-alive", + "Content-Type": "text/event-stream", + }, + status: 200, + }); + return res as BrandSerialized; + }; +}