UNPKG

async-streamify

Version:

Stream and serialize nested promises and async iterables over HTTP, workers, etc

207 lines (206 loc) 7.58 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AsyncObjectDeserializer = void 0; exports.deserialize = deserialize; const bufferedAsyncIterable_js_1 = __importDefault(require("../util/bufferedAsyncIterable.js")); const errorKind = __importStar(require("../kinds/error.js")); /** * Deserializes objects that were serialized using AsyncObjectSerializer. * Reconstructs promises and async iterables from their serialized representations. * * @template TTarget - The expected type of the deserialized object * * @example * ```typescript * const serializedStream = new AsyncObjectSerializer({ * value: Promise.resolve(42), * stream: (async function*() { yield 1; yield 2; })() * }); * * const deserializer = new AsyncObjectDeserializer(serializedStream); * const result = await deserializer.deserialize(); * * console.log(await result.value); // 42 * for await (const num of result.stream) { * console.log(num); // 1, 2 * } * ``` */ class AsyncObjectDeserializer { /** * Creates a new AsyncObjectDeserializer instance * @param iter - The async iterable containing serialized object chunks */ constructor(iter) { Object.defineProperty(this, "iter", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "deserializedRoot", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "promises", { enumerable: true, configurable: true, writable: true, value: new Map() }); Object.defineProperty(this, "activeIterators", { enumerable: true, configurable: true, writable: true, value: new Map() }); /** * Optional callback that is called when the root object is first deserialized */ Object.defineProperty(this, "onSetRoot", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.iter = iter; } /** * Processes the serialized stream and reconstructs the original object * with its promises and async iterables * * @returns Promise<TTarget | undefined> The deserialized object */ async deserialize() { for await (const serializedChunk of this.iter) { if (!this.deserializedRoot) { this.deserializedRoot = this.deserializeValue(serializedChunk); if (this.onSetRoot) this.onSetRoot(this.deserializedRoot); continue; } const [id, value] = serializedChunk; const promise = this.promises.get(id); if (promise) { if ("$resolve" in value) { promise.resolve(this.deserializeValue(value["$resolve"])); } else if ("$reject" in value) { promise.reject(errorKind.maybeDeserialize(value["$reject"])); } else { throw new Error("Unexpected promise state: " + JSON.stringify(value)); } this.promises.delete(id); continue; } const iter = this.activeIterators.get(id); if (iter) { const { done, value: iterValue } = value; if (done) { iter.close(); } else { iter.push(this.deserializeValue(iterValue)); } continue; } } return this.deserializedRoot; } /** * Recursively deserializes a value, reconstructing promises, async iterables, * and nested objects from their serialized form * * @param serializedValue - The serialized value to deserialize * @returns The deserialized value */ deserializeValue(serializedValue) { // Return primitives as-is: null, undefined, numbers, strings, etc. if (typeof serializedValue !== "object" || serializedValue === null) { return serializedValue; } if (Array.isArray(serializedValue)) { return serializedValue.map((value) => this.deserializeValue(value)); } const keys = Object.keys(serializedValue); if (keys.length === 1) { if ("$promise" in serializedValue) { const id = serializedValue["$promise"]; return new Promise((resolve, reject) => { this.promises.set(id, { resolve, reject }); }); } if ("$asyncIterator" in serializedValue) { const id = serializedValue["$asyncIterator"]; const iter = new bufferedAsyncIterable_js_1.default(); this.activeIterators.set(id, iter); return iter; } } if (serializedValue.constructor === Object) { return Object.fromEntries(Object.entries(serializedValue).map(([key, value]) => [ key, this.deserializeValue(value), ])); } return serializedValue; } } exports.AsyncObjectDeserializer = AsyncObjectDeserializer; /** * Helper function to deserialize an object stream and return a promise that * resolves with the root object as soon as it's available * * @template T - The expected type of the deserialized object * @param iter - The async iterable containing serialized object chunks * @returns Promise<T> A promise that resolves with the deserialized root object * * @example * ```typescript * const serializedStream = new AsyncObjectSerializer({ * name: "test", * value: Promise.resolve(42) * }); * * const obj = await deserialize<{ name: string, value: Promise<number> }>(serializedStream); * console.log(obj.name); // "test" * console.log(await obj.value); // 42 * ``` */ function deserialize(iter) { const deserializer = new AsyncObjectDeserializer(iter); return new Promise((resolve) => { deserializer.onSetRoot = resolve; deserializer.deserialize(); // don't await }); } exports.default = deserialize;