UNPKG

@solid/community-server

Version:

Community Solid Server: an open and modular implementation of the Solid specifications

190 lines 7.25 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.endOfStream = void 0; exports.arrayifyStream = arrayifyStream; exports.readableToString = readableToString; exports.readableToQuads = readableToQuads; exports.readJsonStream = readJsonStream; exports.getSingleItem = getSingleItem; exports.pipeSafely = pipeSafely; exports.transformSafely = transformSafely; exports.guardedStreamFrom = guardedStreamFrom; const node_stream_1 = require("node:stream"); const node_util_1 = require("node:util"); const node_events_1 = require("node:events"); const end_of_stream_1 = __importDefault(require("end-of-stream")); const n3_1 = require("n3"); const pump_1 = __importDefault(require("pump")); const global_logger_factory_1 = require("global-logger-factory"); const HttpRequest_1 = require("../server/HttpRequest"); const InternalServerError_1 = require("./errors/InternalServerError"); const GuardedStream_1 = require("./GuardedStream"); exports.endOfStream = (0, node_util_1.promisify)(end_of_stream_1.default); const logger = (0, global_logger_factory_1.getLoggerFor)('StreamUtil'); /** * Converts the stream into an array. * * @param stream - Stream to convert. * * @returns Array containing the stream entries. */ async function arrayifyStream(stream) { const result = []; const dataIt = (0, node_events_1.on)(stream, 'data'); // eslint-disable-next-line ts/no-misused-promises stream.on('end', async () => { await dataIt.return(); }); for await (const entry of dataIt) { result.push(...entry); } return result; } /** * Joins all strings of a stream. * * @param stream - Stream of strings. * * @returns The joined string. */ async function readableToString(stream) { return (await arrayifyStream(stream)).join(''); } /** * Imports quads from a stream into a Store. * * @param stream - Stream of quads. * * @returns A Store containing all the quads. */ async function readableToQuads(stream) { const quads = new n3_1.Store(); quads.import(stream); await (0, exports.endOfStream)(stream); return quads; } /** * Interprets the stream as JSON and converts it to a Dict. * * @param stream - Stream of JSON data. * * @returns The parsed object. */ async function readJsonStream(stream) { const body = await readableToString(stream); return JSON.parse(body); } /** * Converts the stream to a single object. * This assumes the stream is in object mode and only contains a single element, * otherwise an error will be thrown. * * @param stream - Object stream with single entry. */ async function getSingleItem(stream) { const items = await arrayifyStream(stream); if (items.length !== 1) { throw new InternalServerError_1.InternalServerError('Expected a stream with a single object.'); } return items[0]; } // These error messages usually indicate expected behaviour so should not give a warning. // We compare against the error message instead of the code // since the second one is from an external library that does not assign an error code. // At the time of writing the first one gets thrown in Node 16 and the second one in Node 14. const safeErrors = new Set([ 'Cannot call write after a stream was destroyed', 'premature close', ]); /** * Pipes one stream into another and emits errors of the first stream with the second. * If the first stream errors, the second one will be destroyed with the given error. * This will also make the stream {@link Guarded}. * * @param readable - Initial readable stream. * @param destination - The destination for writing data. * @param mapError - Optional function that takes the error and converts it to a new error. * * @returns The destination stream. */ function pipeSafely(readable, destination, mapError) { // We never want to closes the incoming HttpRequest if there is an error // since that also closes the outgoing HttpResponse. // Since `pump` sends stream errors both up and down the pipe chain, // in this case we need to make sure the error only goes down the chain. if ((0, HttpRequest_1.isHttpRequest)(readable)) { readable.pipe(destination); readable.on('error', (error) => { logger.warn(`HttpRequest errored with ${error.message}`); // From https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options : // One important caveat is that if the Readable stream emits an error during processing, // the Writable destination is not closed automatically. If an error occurs, // it will be necessary to manually close each stream in order to prevent memory leaks. destination.destroy(mapError ? mapError(error) : error); }); } else { // In case the input readable is guarded, it will no longer log errors since `pump` attaches a new error listener (0, pump_1.default)(readable, destination, (error) => { if (error) { const msg = `Piped stream errored with ${error.message}`; logger.log(safeErrors.has(error.message) ? 'debug' : 'warn', msg); // Make sure the final error can be handled in a normal streaming fashion destination.emit('error', mapError ? mapError(error) : error); } }); } // Guarding the stream now means the internal error listeners of pump will be ignored // when checking if there is a valid error listener. return (0, GuardedStream_1.guardStream)(destination); } /** * Transforms a stream, ensuring that all errors are forwarded. * * @param source - The stream to be transformed. * @param options - The transformation options. * @param options.transform - The transform function to use. * @param options.flush - The flush function to use. * * @returns The transformed stream */ function transformSafely(source, { transform = function (data) { this.push(data); }, flush = () => null, ...options } = {}) { return pipeSafely(source, new node_stream_1.Transform({ ...options, async transform(data, encoding, callback) { let error = null; try { await transform.call(this, data, encoding); } catch (err) { error = err; } callback(error); }, async flush(callback) { let error = null; try { await flush.call(this); } catch (err) { error = err; } callback(error); }, })); } /** * Converts a string or array to a stream and applies an error guard so that it is {@link Guarded}. * * @param contents - Data to stream. * @param options - Options to pass to the Readable constructor. See {@link Readable.from}. */ function guardedStreamFrom(contents, options) { return (0, GuardedStream_1.guardStream)(node_stream_1.Readable.from(typeof contents === 'string' ? [contents] : contents, options)); } //# sourceMappingURL=StreamUtil.js.map