UNPKG

tupleson

Version:

A hackable JSON serializer/deserializer

1 lines 5.81 kB
{"version":3,"sources":["../../src/async/iterableUtils.ts"],"sourcesContent":["import { assert } from \"../internals/assert.js\";\nimport {\n\tNodeJSReadableStreamEsque,\n\tWebReadableStreamEsque,\n} from \"../internals/esque.js\";\n\nexport async function* readableStreamToAsyncIterable<T>(\n\tstream:\n\t\t| NodeJSReadableStreamEsque\n\t\t| ReadableStream<T>\n\t\t| WebReadableStreamEsque,\n): AsyncIterable<T> {\n\tif (Symbol.asyncIterator in stream) {\n\t\t// NodeJS.ReadableStream\n\t\tfor await (const chunk of stream) {\n\t\t\tyield chunk as T;\n\t\t}\n\n\t\treturn;\n\t}\n\n\t// Get a lock on the stream\n\tconst reader = stream.getReader();\n\n\ttry {\n\t\t// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n\t\twhile (true) {\n\t\t\t// Read from the stream\n\t\t\tconst result = await reader.read();\n\n\t\t\t// Exit if we're done\n\t\t\tif (result.done) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Else yield the chunk\n\t\t\tyield result.value as T;\n\t\t}\n\t} finally {\n\t\treader.releaseLock();\n\t}\n}\n\nexport async function* mapIterable<T, TValue>(\n\titerable: AsyncIterable<T>,\n\tfn: (v: T) => TValue,\n): AsyncIterable<TValue> {\n\tfor await (const value of iterable) {\n\t\tyield fn(value);\n\t}\n}\n\nexport function createReadableStream<TValue = unknown>() {\n\tlet controller: ReadableStreamDefaultController<TValue> =\n\t\tnull as unknown as ReadableStreamDefaultController<TValue>;\n\tconst stream = new ReadableStream<TValue>({\n\t\tstart(c) {\n\t\t\tcontroller = c;\n\t\t},\n\t});\n\n\tassert(controller, `Could not find controller - this is a bug`);\n\n\treturn [stream, controller] as const;\n}\n\n/**\n * Creates an event that adheres to the [Event Stream format](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#event_stream_format)\n *\n * When called without any arguments, it returns a keep-alive event.\n * @param opts {{ data?: TData; event?: TEvent; id?: TId; retry?: TRetry }}\n * @param opts.data The data to send to the client. This value will be serialized to JSON.\n * @param opts.event The type of event to send to the client. Defaults to `message`.\n * @param opts.id The id of the event to send to the client, used to resume the connection.\n * @param opts.retry The reconnection time. If the connection to the server is lost, the\n * browser will wait for the specified time before attempting to reconnect.\n */\nexport function createServerEvent<\n\tconst TData,\n\tconst TEvent extends string,\n\tconst TId extends string,\n\tconst TRetry extends number,\n>(opts: {\n\tdata?: TData;\n\t/**\n\t * The type of event to send to the client. Defaults to `message`.\n\t * @default \"message\" when any other field is set, undefined otherwise\n\t * @example ```ts\n\t * /// on the server\n\t * createServerEvent({ event: \"answer\", data: 42 })\n\t * createServerEvent({ event: \"close\" })\n\t * /// on the client\n\t * const eventSource = new EventSource(\"/sse\");\n\t * let answer;\n\t * eventSource.addEventListener(\"answer\", (e) => {\n\t * \tanswer = e.data;\n\t * })\n\t * eventSource.addEventListener(\"close\", () => {\n\t * \teventSource.close();\n\t * })\n\t * ```\n\t */\n\tevent?: TEvent;\n\t/**\n\t * The id of the event to send to the client, used to resume the connection.\n\t * When the EventSource client reconnects, it will send the last id it\n\t * received via the `Last-Event-ID` header (though the header can also be\n\t * set manually). The server will then resume the connection and send all\n\t * events that happened since the last event with that id.\n\t * @default undefined\n\t */\n\tid?: TId;\n\t/**\n\t * The reconnection time. If the connection to the server is lost, the\n\t * browser will wait for the specified time before attempting to reconnect.\n\t * This must be an integer, specifying the reconnection time in\n\t * milliseconds. If a non-integer value is specified it will be rounded\n\t * down to the nearest integer. The default value is 1000 ms (1 second).\n\t */\n\tretry?: TRetry;\n}): string {\n\tconst { data, event, id, retry } = opts;\n\n\t// Lines starting with a colon are essentially comments, and are ignored.\n\t// An event consisting solely of a comment is equivalent to a keep-alive.\n\t// By setting\n\tconst emptyLine = \":\\n\";\n\n\treturn (\n\t\temptyLine +\n\t\taddIfProvided(\"event\", event) +\n\t\taddIfProvided(\"id\", id) +\n\t\taddIfProvided(\"retry\", retry) +\n\t\taddIfProvided(\"data\", data) +\n\t\t\"\\n\"\n\t);\n}\n\nfunction addIfProvided<TKey extends \"data\" | \"event\" | \"id\" | \"retry\">(\n\tkey: TKey,\n\tvalue: Parameters<typeof createServerEvent>[0][TKey],\n) {\n\tif (value === undefined) {\n\t\treturn \"\";\n\t}\n\n\tif (key === \"data\") {\n\t\treturn `data: ${JSON.stringify(value)}\\n`;\n\t}\n\n\treturn `${key}: ${value as any}\\n`;\n}\n"],"mappings":"AAAA,SAAS,cAAc;AAMvB,gBAAuB,8BACtB,QAImB;AACnB,MAAI,OAAO,iBAAiB,QAAQ;AAEnC,qBAAiB,SAAS,QAAQ;AACjC,YAAM;AAAA,IACP;AAEA;AAAA,EACD;AAGA,QAAM,SAAS,OAAO,UAAU;AAEhC,MAAI;AAEH,WAAO,MAAM;AAEZ,YAAM,SAAS,MAAM,OAAO,KAAK;AAGjC,UAAI,OAAO,MAAM;AAChB;AAAA,MACD;AAGA,YAAM,OAAO;AAAA,IACd;AAAA,EACD,UAAE;AACD,WAAO,YAAY;AAAA,EACpB;AACD;AAEA,gBAAuB,YACtB,UACA,IACwB;AACxB,mBAAiB,SAAS,UAAU;AACnC,UAAM,GAAG,KAAK;AAAA,EACf;AACD;AAEO,SAAS,uBAAyC;AACxD,MAAI,aACH;AACD,QAAM,SAAS,IAAI,eAAuB;AAAA,IACzC,MAAM,GAAG;AACR,mBAAa;AAAA,IACd;AAAA,EACD,CAAC;AAED,SAAO,YAAY,2CAA2C;AAE9D,SAAO,CAAC,QAAQ,UAAU;AAC3B;AAaO,SAAS,kBAKd,MAsCS;AACV,QAAM,EAAE,MAAM,OAAO,IAAI,MAAM,IAAI;AAKnC,QAAM,YAAY;AAElB,SACC,YACA,cAAc,SAAS,KAAK,IAC5B,cAAc,MAAM,EAAE,IACtB,cAAc,SAAS,KAAK,IAC5B,cAAc,QAAQ,IAAI,IAC1B;AAEF;AAEA,SAAS,cACR,KACA,OACC;AACD,MAAI,UAAU,QAAW;AACxB,WAAO;AAAA,EACR;AAEA,MAAI,QAAQ,QAAQ;AACnB,WAAO,SAAS,KAAK,UAAU,KAAK,CAAC;AAAA;AAAA,EACtC;AAEA,SAAO,GAAG,GAAG,KAAK,KAAY;AAAA;AAC/B;","names":[]}