@solid/community-server
Version:
Community Solid Server: an open and modular implementation of the Solid specifications
190 lines • 7.25 kB
JavaScript
;
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