UNPKG

nest-ndjson-req-stream

Version:

Accept and automatically parse NDJSON request streams.

95 lines (91 loc) 3.06 kB
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 };