nest-ndjson-req-stream
Version:
Accept and automatically parse NDJSON request streams.
95 lines (91 loc) • 3.06 kB
JavaScript
import { Injectable, createParamDecorator, BadRequestException } from '@nestjs/common';
import { Transform } from 'stream';
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __decorateClass = (decorators, target, key, kind) => {
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
for (var i = decorators.length - 1, decorator; i >= 0; i--)
if (decorator = decorators[i])
result = (decorator(result)) || result;
return result;
};
var NdJsonStreamParser = class {
/**
* Creates a Transform stream that parses NDJSON data
* @template T - The type of objects in the NDJSON stream
* @returns A Transform stream that emits parsed objects
*/
static createParser() {
let buffer = "";
let itemCount = 0;
return new Transform({
objectMode: true,
transform(chunk, encoding, callback) {
buffer += chunk.toString();
const lines = buffer.split("\n");
buffer = lines.pop() || "";
for (const line of lines) {
itemCount++;
if (line.trim()) {
try {
const parsed = JSON.parse(line);
this.push(parsed);
} catch (error) {
this.emit(`Error occurred parsing stream item ${itemCount}: ${error.message}`);
}
}
}
callback();
},
flush(callback) {
if (buffer.trim()) {
try {
this.push(JSON.parse(buffer));
} catch (error) {
this.emit(`Error occurred parsing stream item ${itemCount}: ${error.message}`);
}
}
callback();
}
});
}
/**
* Parses a readable stream as NDJSON and yields parsed objects
* @template T - The type of objects in the NDJSON stream
* @param stream - The readable stream containing NDJSON data
* @returns An async generator that yields parsed objects
*/
static async *parseStream(stream) {
const parser = this.createParser();
const errors = [];
parser.on("parse-error", (error) => errors.push(error));
stream.pipe(parser);
for await (const item of parser) {
yield item;
}
if (errors.length > 0) {
console.warn("Parse errors encountered:", errors);
}
}
};
NdJsonStreamParser = __decorateClass([
Injectable()
], NdJsonStreamParser);
// src/decorators/ndjson-stream.decorator.ts
var NdJsonStreamReq = createParamDecorator(
(data, ctx) => {
const request = ctx.switchToHttp().getRequest();
const batchSize = data?.batchSize ?? 25;
const contentType = request.headers["content-type"];
if (!contentType.toLowerCase().includes("application/x-ndjson")) {
throw new BadRequestException(
`Invalid content-type: ${contentType}. Expected application/x-ndjson`
);
}
const asyncGenerator = NdJsonStreamParser.parseStream(request);
const streamRequest = request;
streamRequest.body = asyncGenerator;
streamRequest.batchSize = batchSize;
return streamRequest;
}
);
export { NdJsonStreamReq };