UNPKG

tupleson

Version:

A hackable JSON serializer/deserializer

1 lines 14.3 kB
{"version":3,"sources":["../../src/async/serializeAsync.ts"],"sourcesContent":["import { TsonCircularReferenceError } from \"../errors.js\";\nimport { assert } from \"../internals/assert.js\";\nimport { GetNonce, getDefaultNonce } from \"../internals/getNonce.js\";\nimport { mapOrReturn } from \"../internals/mapOrReturn.js\";\nimport {\n\tTsonAllTypes,\n\tTsonNonce,\n\tTsonSerialized,\n\tTsonSerializedValue,\n\tTsonTuple,\n\tTsonTypeHandlerKey,\n\tTsonTypeTesterCustom,\n\tTsonTypeTesterPrimitive,\n} from \"../sync/syncTypes.js\";\nimport { TsonStreamInterruptedError } from \"./asyncErrors.js\";\nimport {\n\tBrandSerialized,\n\tTsonAsyncIndex,\n\tTsonAsyncOptions,\n\tTsonAsyncStringifier,\n} from \"./asyncTypes.js\";\nimport { createReadableStream, createServerEvent } from \"./iterableUtils.js\";\n\ntype WalkFn = (value: unknown) => unknown;\n\nexport type TsonAsyncValueTuple = [TsonAsyncIndex, unknown];\n\nfunction walkerFactory(nonce: TsonNonce, types: TsonAsyncOptions[\"types\"]) {\n\t// instance variables\n\tlet asyncIndex = 0;\n\tconst seen = new WeakSet();\n\tconst cache = new WeakMap<object, unknown>();\n\n\tconst iterators = new Map<TsonAsyncIndex, AsyncIterator<unknown>>();\n\n\tconst iterator = {\n\t\tasync *[Symbol.asyncIterator]() {\n\t\t\t// race all active iterators and yield next value as they come\n\t\t\t// when one iterator is done, remove it from the list\n\n\t\t\t// when all iterators are done, we're done\n\n\t\t\tconst nextAsyncIteratorValue = new Map<\n\t\t\t\tTsonAsyncIndex,\n\t\t\t\tPromise<[TsonAsyncIndex, IteratorResult<unknown>]>\n\t\t\t>();\n\n\t\t\t// let _tmp = 0;\n\n\t\t\twhile (iterators.size > 0) {\n\t\t\t\t// if (_tmp++ > 10) {\n\t\t\t\t// \tthrow new Error(\"too many iterations\");\n\t\t\t\t// }\n\n\t\t\t\t// set next cursor\n\t\t\t\tfor (const [idx, iterator] of iterators) {\n\t\t\t\t\tif (!nextAsyncIteratorValue.has(idx)) {\n\t\t\t\t\t\tnextAsyncIteratorValue.set(\n\t\t\t\t\t\t\tidx,\n\t\t\t\t\t\t\titerator.next().then((result) => [idx, result]),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst nextValues = Array.from(nextAsyncIteratorValue.values());\n\n\t\t\t\tconst [idx, result] = await Promise.race(nextValues);\n\n\t\t\t\tif (result.done) {\n\t\t\t\t\tnextAsyncIteratorValue.delete(idx);\n\t\t\t\t\titerators.delete(idx);\n\t\t\t\t\tcontinue;\n\t\t\t\t} else {\n\t\t\t\t\tconst iterator = iterators.get(idx);\n\n\t\t\t\t\tassert(iterator, `iterator ${idx} not found`);\n\t\t\t\t\tnextAsyncIteratorValue.set(\n\t\t\t\t\t\tidx,\n\t\t\t\t\t\titerator.next().then((result) => [idx, result]),\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst valueTuple: TsonAsyncValueTuple = [idx, walk(result.value)];\n\t\t\t\tyield valueTuple;\n\t\t\t}\n\t\t},\n\t};\n\n\tconst handlers = (() => {\n\t\tconst all = types.map((handler) => {\n\t\t\ttype Serializer = (\n\t\t\t\tvalue: unknown,\n\t\t\t\tnonce: TsonNonce,\n\t\t\t\twalk: WalkFn,\n\t\t\t) => TsonSerializedValue;\n\n\t\t\tconst $serialize: Serializer = handler.serializeIterator\n\t\t\t\t? (value): TsonTuple => {\n\t\t\t\t\t\tconst idx = asyncIndex++ as TsonAsyncIndex;\n\n\t\t\t\t\t\tconst iterator = handler.serializeIterator({\n\t\t\t\t\t\t\tvalue,\n\t\t\t\t\t\t});\n\t\t\t\t\t\titerators.set(idx, iterator[Symbol.asyncIterator]());\n\n\t\t\t\t\t\treturn [handler.key as TsonTypeHandlerKey, idx, nonce];\n\t\t\t\t }\n\t\t\t\t: handler.serialize\n\t\t\t\t? (value, nonce, walk): TsonTuple => [\n\t\t\t\t\t\thandler.key as TsonTypeHandlerKey,\n\t\t\t\t\t\twalk(handler.serialize(value)),\n\t\t\t\t\t\tnonce,\n\t\t\t\t ]\n\t\t\t\t: (value, _nonce, walk) => walk(value);\n\t\t\treturn {\n\t\t\t\t...handler,\n\t\t\t\t$serialize,\n\t\t\t};\n\t\t});\n\t\ttype Handler = (typeof all)[number];\n\n\t\tconst byPrimitive: Partial<\n\t\t\tRecord<TsonAllTypes, Extract<Handler, TsonTypeTesterPrimitive>>\n\t\t> = {};\n\t\tconst nonPrimitive: Extract<Handler, TsonTypeTesterCustom>[] = [];\n\n\t\tfor (const handler of all) {\n\t\t\tif (handler.primitive) {\n\t\t\t\tif (byPrimitive[handler.primitive]) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Multiple handlers for primitive ${handler.primitive} found`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tbyPrimitive[handler.primitive] = handler;\n\t\t\t} else {\n\t\t\t\tnonPrimitive.push(handler);\n\t\t\t}\n\t\t}\n\n\t\treturn [nonPrimitive, byPrimitive] as const;\n\t})();\n\n\tconst [nonPrimitive, byPrimitive] = handlers;\n\n\tconst walk: WalkFn = (value) => {\n\t\tconst type = typeof value;\n\t\tconst isComplex = !!value && type === \"object\";\n\n\t\tif (isComplex) {\n\t\t\tif (seen.has(value)) {\n\t\t\t\tconst cached = cache.get(value);\n\t\t\t\tif (!cached) {\n\t\t\t\t\tthrow new TsonCircularReferenceError(value);\n\t\t\t\t}\n\n\t\t\t\treturn cached;\n\t\t\t}\n\n\t\t\tseen.add(value);\n\t\t}\n\n\t\tconst cacheAndReturn = (result: unknown) => {\n\t\t\tif (isComplex) {\n\t\t\t\tcache.set(value, result);\n\t\t\t}\n\n\t\t\treturn result;\n\t\t};\n\n\t\tconst primitiveHandler = byPrimitive[type];\n\t\tif (\n\t\t\tprimitiveHandler &&\n\t\t\t(!primitiveHandler.test || primitiveHandler.test(value))\n\t\t) {\n\t\t\treturn cacheAndReturn(primitiveHandler.$serialize(value, nonce, walk));\n\t\t}\n\n\t\tfor (const handler of nonPrimitive) {\n\t\t\tif (handler.test(value)) {\n\t\t\t\treturn cacheAndReturn(handler.$serialize(value, nonce, walk));\n\t\t\t}\n\t\t}\n\n\t\treturn cacheAndReturn(mapOrReturn(value, walk));\n\t};\n\n\treturn [walk, iterator] as const;\n}\n\ntype TsonAsyncSerializer = <T>(\n\tvalue: T,\n) => [TsonSerialized<T>, AsyncIterable<TsonAsyncValueTuple>];\n\nexport function createAsyncTsonSerialize(\n\topts: TsonAsyncOptions,\n): TsonAsyncSerializer {\n\tconst getNonce: GetNonce = (opts.nonce ?? getDefaultNonce) as GetNonce;\n\treturn (value) => {\n\t\tconst nonce = getNonce();\n\t\tconst [walk, iterator] = walkerFactory(nonce, opts.types);\n\n\t\treturn [\n\t\t\t{\n\t\t\t\tjson: walk(value),\n\t\t\t\tnonce,\n\t\t\t} as TsonSerialized<typeof value>,\n\t\t\titerator,\n\t\t];\n\t};\n}\n\n/**\n * JSON stream\n */\nexport function createTsonStreamAsync(\n\topts: TsonAsyncOptions,\n): TsonAsyncStringifier {\n\tconst indent = (length: number) => \" \".repeat(length);\n\tconst stringifier: (value: unknown, space?: number) => AsyncIterable<string> =\n\t\tasync function* stringify(value, space = 0) {\n\t\t\t// head looks like\n\n\t\t\t// [\n\t\t\t// \t\t{ json: {}, nonce: \"...\" }\n\t\t\t// \t,[\n\n\t\t\tconst [head, iterator] = createAsyncTsonSerialize(opts)(value);\n\n\t\t\t// first line of the json: init the array, ignored when parsing>\n\t\t\tyield \"[\" + \"\\n\";\n\t\t\t// second line: the shape of the json - used when parsing>\n\t\t\tyield indent(space * 1) + JSON.stringify(head) + \"\\n\";\n\n\t\t\t// third line: comma before values, ignored when parsing\n\t\t\tyield indent(space * 1) + \",\" + \"\\n\";\n\t\t\t// fourth line: the values array, ignored when parsing\n\t\t\tyield indent(space * 1) + \"[\" + \"\\n\";\n\n\t\t\tlet isFirstStreamedValue = true;\n\n\t\t\tfor await (const value of iterator) {\n\t\t\t\tconst prefix = indent(space * 2) + (isFirstStreamedValue ? \"\" : \",\");\n\n\t\t\t\tyield prefix + JSON.stringify(value) + \"\\n\";\n\n\t\t\t\tisFirstStreamedValue = false;\n\t\t\t}\n\n\t\t\tyield \"]]\" + \"\\n\"; // end response and value array\n\t\t};\n\n\treturn stringifier as TsonAsyncStringifier;\n}\n\nexport function createTsonSSEResponse(opts: TsonAsyncOptions) {\n\tconst serialize = createAsyncTsonSerialize(opts);\n\n\treturn <TValue>(value: TValue) => {\n\t\tconst [readable, controller] = createReadableStream();\n\n\t\tasync function iterate() {\n\t\t\tconst [head, iterable] = serialize(value);\n\n\t\t\tcontroller.enqueue(\n\t\t\t\tcreateServerEvent({\n\t\t\t\t\tdata: head,\n\t\t\t\t\t//event: \"head\",\n\t\t\t\t\t// id: \"0\",\n\t\t\t\t\t// retry: 0,\n\t\t\t\t}),\n\t\t\t);\n\t\t\tfor await (const chunk of iterable) {\n\t\t\t\tcontroller.enqueue(\n\t\t\t\t\tcreateServerEvent({\n\t\t\t\t\t\tdata: chunk,\n\t\t\t\t\t\t// event: \"tson\",\n\t\t\t\t\t\t// id: \"0\",\n\t\t\t\t\t\t// retry: 0,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// indicate the end of the stream\n\n\t\t\tcontroller.enqueue(\n\t\t\t\tcreateServerEvent({\n\t\t\t\t\tdata: null,\n\t\t\t\t\tevent: \"close\",\n\t\t\t\t\t// id: \"0\",\n\t\t\t\t\t// retry: 0,\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\tcontroller.close();\n\n\t\t\tcontroller.error(\n\t\t\t\tnew TsonStreamInterruptedError(new Error(\"SSE stream ended\")),\n\t\t\t);\n\t\t}\n\n\t\titerate().catch((err) => {\n\t\t\tcontroller.error(err);\n\t\t});\n\n\t\tconst res = new Response(readable, {\n\t\t\theaders: {\n\t\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t\t\tConnection: \"keep-alive\",\n\t\t\t\t\"Content-Type\": \"text/event-stream\",\n\t\t\t\t// prevent buffering by nginx\n\t\t\t\t\"X-Accel-Buffering\": \"no\",\n\t\t\t},\n\t\t\tstatus: 200,\n\t\t});\n\t\treturn res as BrandSerialized<typeof res, TValue>;\n\t};\n}\n\n/**\n * JSON stream Response\n */\nexport function createTsonSerializeJsonStreamResponse(opts: TsonAsyncOptions) {\n\tconst serialize = createTsonStreamAsync(opts);\n\n\treturn <TValue>(value: TValue) => {\n\t\tconst [readable, controller] = createReadableStream<string>();\n\n\t\tasync function iterate() {\n\t\t\tfor await (const chunk of serialize(value)) {\n\t\t\t\tcontroller.enqueue(chunk);\n\t\t\t}\n\n\t\t\tcontroller.close();\n\t\t}\n\n\t\titerate().catch((err) => {\n\t\t\tcontroller.error(err);\n\t\t});\n\n\t\tconst res = new Response(readable, {\n\t\t\theaders: {\n\t\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t\t\tConnection: \"keep-alive\",\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t},\n\t\t\tstatus: 200,\n\t\t});\n\t\treturn res as BrandSerialized<typeof res, TValue>;\n\t};\n}\n"],"mappings":"AAAA,SAAS,kCAAkC;AAC3C,SAAS,cAAc;AACvB,SAAmB,uBAAuB;AAC1C,SAAS,mBAAmB;AAW5B,SAAS,kCAAkC;AAO3C,SAAS,sBAAsB,yBAAyB;AAMxD,SAAS,cAAc,OAAkB,OAAkC;AAE1E,MAAI,aAAa;AACjB,QAAM,OAAO,oBAAI,QAAQ;AACzB,QAAM,QAAQ,oBAAI,QAAyB;AAE3C,QAAM,YAAY,oBAAI,IAA4C;AAElE,QAAM,WAAW;AAAA,IAChB,QAAQ,OAAO,aAAa,IAAI;AAM/B,YAAM,yBAAyB,oBAAI,IAGjC;AAIF,aAAO,UAAU,OAAO,GAAG;AAM1B,mBAAW,CAACA,MAAKC,SAAQ,KAAK,WAAW;AACxC,cAAI,CAAC,uBAAuB,IAAID,IAAG,GAAG;AACrC,mCAAuB;AAAA,cACtBA;AAAA,cACAC,UAAS,KAAK,EAAE,KAAK,CAACC,YAAW,CAACF,MAAKE,OAAM,CAAC;AAAA,YAC/C;AAAA,UACD;AAAA,QACD;AAEA,cAAM,aAAa,MAAM,KAAK,uBAAuB,OAAO,CAAC;AAE7D,cAAM,CAAC,KAAK,MAAM,IAAI,MAAM,QAAQ,KAAK,UAAU;AAEnD,YAAI,OAAO,MAAM;AAChB,iCAAuB,OAAO,GAAG;AACjC,oBAAU,OAAO,GAAG;AACpB;AAAA,QACD,OAAO;AACN,gBAAMD,YAAW,UAAU,IAAI,GAAG;AAElC,iBAAOA,WAAU,YAAY,GAAG,YAAY;AAC5C,iCAAuB;AAAA,YACtB;AAAA,YACAA,UAAS,KAAK,EAAE,KAAK,CAACC,YAAW,CAAC,KAAKA,OAAM,CAAC;AAAA,UAC/C;AAAA,QACD;AAEA,cAAM,aAAkC,CAAC,KAAK,KAAK,OAAO,KAAK,CAAC;AAChE,cAAM;AAAA,MACP;AAAA,IACD;AAAA,EACD;AAEA,QAAM,YAAY,MAAM;AACvB,UAAM,MAAM,MAAM,IAAI,CAAC,YAAY;AAOlC,YAAM,aAAyB,QAAQ,oBACpC,CAAC,UAAqB;AACtB,cAAM,MAAM;AAEZ,cAAMD,YAAW,QAAQ,kBAAkB;AAAA,UAC1C;AAAA,QACD,CAAC;AACD,kBAAU,IAAI,KAAKA,UAAS,OAAO,aAAa,EAAE,CAAC;AAEnD,eAAO,CAAC,QAAQ,KAA2B,KAAK,KAAK;AAAA,MACrD,IACA,QAAQ,YACR,CAAC,OAAOE,QAAOC,UAAoB;AAAA,QACnC,QAAQ;AAAA,QACRA,MAAK,QAAQ,UAAU,KAAK,CAAC;AAAA,QAC7BD;AAAA,MACA,IACA,CAAC,OAAO,QAAQC,UAASA,MAAK,KAAK;AACtC,aAAO;AAAA,QACN,GAAG;AAAA,QACH;AAAA,MACD;AAAA,IACD,CAAC;AAGD,UAAMC,eAEF,CAAC;AACL,UAAMC,gBAAyD,CAAC;AAEhE,eAAW,WAAW,KAAK;AAC1B,UAAI,QAAQ,WAAW;AACtB,YAAID,aAAY,QAAQ,SAAS,GAAG;AACnC,gBAAM,IAAI;AAAA,YACT,mCAAmC,QAAQ,SAAS;AAAA,UACrD;AAAA,QACD;AAEA,QAAAA,aAAY,QAAQ,SAAS,IAAI;AAAA,MAClC,OAAO;AACN,QAAAC,cAAa,KAAK,OAAO;AAAA,MAC1B;AAAA,IACD;AAEA,WAAO,CAACA,eAAcD,YAAW;AAAA,EAClC,GAAG;AAEH,QAAM,CAAC,cAAc,WAAW,IAAI;AAEpC,QAAM,OAAe,CAAC,UAAU;AAC/B,UAAM,OAAO,OAAO;AACpB,UAAM,YAAY,CAAC,CAAC,SAAS,SAAS;AAEtC,QAAI,WAAW;AACd,UAAI,KAAK,IAAI,KAAK,GAAG;AACpB,cAAM,SAAS,MAAM,IAAI,KAAK;AAC9B,YAAI,CAAC,QAAQ;AACZ,gBAAM,IAAI,2BAA2B,KAAK;AAAA,QAC3C;AAEA,eAAO;AAAA,MACR;AAEA,WAAK,IAAI,KAAK;AAAA,IACf;AAEA,UAAM,iBAAiB,CAAC,WAAoB;AAC3C,UAAI,WAAW;AACd,cAAM,IAAI,OAAO,MAAM;AAAA,MACxB;AAEA,aAAO;AAAA,IACR;AAEA,UAAM,mBAAmB,YAAY,IAAI;AACzC,QACC,qBACC,CAAC,iBAAiB,QAAQ,iBAAiB,KAAK,KAAK,IACrD;AACD,aAAO,eAAe,iBAAiB,WAAW,OAAO,OAAO,IAAI,CAAC;AAAA,IACtE;AAEA,eAAW,WAAW,cAAc;AACnC,UAAI,QAAQ,KAAK,KAAK,GAAG;AACxB,eAAO,eAAe,QAAQ,WAAW,OAAO,OAAO,IAAI,CAAC;AAAA,MAC7D;AAAA,IACD;AAEA,WAAO,eAAe,YAAY,OAAO,IAAI,CAAC;AAAA,EAC/C;AAEA,SAAO,CAAC,MAAM,QAAQ;AACvB;AAMO,SAAS,yBACf,MACsB;AACtB,QAAM,WAAsB,KAAK,SAAS;AAC1C,SAAO,CAAC,UAAU;AACjB,UAAM,QAAQ,SAAS;AACvB,UAAM,CAAC,MAAM,QAAQ,IAAI,cAAc,OAAO,KAAK,KAAK;AAExD,WAAO;AAAA,MACN;AAAA,QACC,MAAM,KAAK,KAAK;AAAA,QAChB;AAAA,MACD;AAAA,MACA;AAAA,IACD;AAAA,EACD;AACD;AAKO,SAAS,sBACf,MACuB;AACvB,QAAM,SAAS,CAAC,WAAmB,IAAI,OAAO,MAAM;AACpD,QAAM,cACL,gBAAgB,UAAU,OAAO,QAAQ,GAAG;AAO3C,UAAM,CAAC,MAAM,QAAQ,IAAI,yBAAyB,IAAI,EAAE,KAAK;AAG7D,UAAM;AAEN,UAAM,OAAO,QAAQ,CAAC,IAAI,KAAK,UAAU,IAAI,IAAI;AAGjD,UAAM,OAAO,QAAQ,CAAC,IAAI;AAE1B,UAAM,OAAO,QAAQ,CAAC,IAAI;AAE1B,QAAI,uBAAuB;AAE3B,qBAAiBE,UAAS,UAAU;AACnC,YAAM,SAAS,OAAO,QAAQ,CAAC,KAAK,uBAAuB,KAAK;AAEhE,YAAM,SAAS,KAAK,UAAUA,MAAK,IAAI;AAEvC,6BAAuB;AAAA,IACxB;AAEA,UAAM;AAAA,EACP;AAED,SAAO;AACR;AAEO,SAAS,sBAAsB,MAAwB;AAC7D,QAAM,YAAY,yBAAyB,IAAI;AAE/C,SAAO,CAAS,UAAkB;AACjC,UAAM,CAAC,UAAU,UAAU,IAAI,qBAAqB;AAEpD,mBAAe,UAAU;AACxB,YAAM,CAAC,MAAM,QAAQ,IAAI,UAAU,KAAK;AAExC,iBAAW;AAAA,QACV,kBAAkB;AAAA,UACjB,MAAM;AAAA;AAAA;AAAA;AAAA,QAIP,CAAC;AAAA,MACF;AACA,uBAAiB,SAAS,UAAU;AACnC,mBAAW;AAAA,UACV,kBAAkB;AAAA,YACjB,MAAM;AAAA;AAAA;AAAA;AAAA,UAIP,CAAC;AAAA,QACF;AAAA,MACD;AAIA,iBAAW;AAAA,QACV,kBAAkB;AAAA,UACjB,MAAM;AAAA,UACN,OAAO;AAAA;AAAA;AAAA,QAGR,CAAC;AAAA,MACF;AAEA,iBAAW,MAAM;AAEjB,iBAAW;AAAA,QACV,IAAI,2BAA2B,IAAI,MAAM,kBAAkB,CAAC;AAAA,MAC7D;AAAA,IACD;AAEA,YAAQ,EAAE,MAAM,CAAC,QAAQ;AACxB,iBAAW,MAAM,GAAG;AAAA,IACrB,CAAC;AAED,UAAM,MAAM,IAAI,SAAS,UAAU;AAAA,MAClC,SAAS;AAAA,QACR,iBAAiB;AAAA,QACjB,YAAY;AAAA,QACZ,gBAAgB;AAAA;AAAA,QAEhB,qBAAqB;AAAA,MACtB;AAAA,MACA,QAAQ;AAAA,IACT,CAAC;AACD,WAAO;AAAA,EACR;AACD;AAKO,SAAS,sCAAsC,MAAwB;AAC7E,QAAM,YAAY,sBAAsB,IAAI;AAE5C,SAAO,CAAS,UAAkB;AACjC,UAAM,CAAC,UAAU,UAAU,IAAI,qBAA6B;AAE5D,mBAAe,UAAU;AACxB,uBAAiB,SAAS,UAAU,KAAK,GAAG;AAC3C,mBAAW,QAAQ,KAAK;AAAA,MACzB;AAEA,iBAAW,MAAM;AAAA,IAClB;AAEA,YAAQ,EAAE,MAAM,CAAC,QAAQ;AACxB,iBAAW,MAAM,GAAG;AAAA,IACrB,CAAC;AAED,UAAM,MAAM,IAAI,SAAS,UAAU;AAAA,MAClC,SAAS;AAAA,QACR,iBAAiB;AAAA,QACjB,YAAY;AAAA,QACZ,gBAAgB;AAAA,MACjB;AAAA,MACA,QAAQ;AAAA,IACT,CAAC;AACD,WAAO;AAAA,EACR;AACD;","names":["idx","iterator","result","nonce","walk","byPrimitive","nonPrimitive","value"]}