@nestjs/common
Version:
Decorators, pipes, exceptions and utilities for NestJS applications: @Controller(), @Injectable(), @Body(), ValidationPipe, HttpException and more.
90 lines (89 loc) • 3.33 kB
JavaScript
import { Readable } from 'stream';
import { types } from 'util';
import { isFunction, isString } from './shared.utils.js';
/**
* A streaming body that yields what `body` does, with `project` applied to
* each value the router sends as a line of JSON: that is any but strings and
* binary data, which it sends as they are.
*
* Nothing is read from `body` before the result is, which it may never be:
* the router stops a body it does not send (e.g. for a HEAD request) unread.
*/
export function mapStreamingBody(body, project) {
// Neither a web `Response` nor a `Readable` that is not in object mode yields
// anything but text and binary data, and a body that has been read already is
// left for the router to refuse.
if (!isFunction(body[Symbol.asyncIterator]) ||
body.readableObjectMode === false ||
body.locked === true ||
Readable.isDisturbed(body)) {
return body;
}
let iterator;
const open = () => (iterator ??= openStreamingBody(body));
const mapResult = async (result) => {
if (result.done || isString(result.value) || isBinary(result.value)) {
return result;
}
try {
return { done: false, value: await project(result.value) };
}
catch (err) {
// As an async generator would: `for await` does not stop an iterator
// that failed, so nothing else may stop the body.
void open()
.return(undefined)
.catch(() => undefined);
throw err;
}
};
// Like an async generator, the result is its own iterator: not every consumer
// (e.g. a GraphQL subscription) asks for `Symbol.asyncIterator`.
return {
[Symbol.asyncIterator]() {
return this;
},
next: async () => mapResult(await open().next()),
return: async (value) => mapResult(await open().return(value)),
throw: async (error) => mapResult(await open().throw(error)),
};
}
function isBinary(value) {
return ArrayBuffer.isView(value) || types.isAnyArrayBuffer(value);
}
/**
* Opens the body for reading, as the router does: neither the async iterator
* of a `ReadableStream` nor that of a `Readable` stops the stream until the
* read in progress settles, which an upstream that went quiet may never do, so
* these are stopped directly. An async generator over `body` would wait too.
*/
function openStreamingBody(body) {
if (isFunction(body.getReader)) {
const reader = body.getReader();
return {
next: () => reader.read(),
return: async (value) => {
await reader.cancel();
return { done: true, value };
},
throw: async (error) => {
await reader.cancel(error);
throw error;
},
};
}
const iterator = body[Symbol.asyncIterator]();
return {
next: () => iterator.next(),
return: async (value) => {
body.destroy?.();
return (await iterator.return?.(value)) ?? { done: true, value };
},
throw: async (error) => {
if (!isFunction(iterator.throw)) {
throw error;
}
return iterator.throw(error);
},
};
}