text-line-transform-stream.ts 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
  2. // This module is browser compatible.
  3. // Modified by Sukka (https://skk.moe) to increase compatibility and performance with Bun.
  4. interface TextLineStreamOptions {
  5. /** Allow splitting by solo \r */
  6. allowCR: boolean;
  7. }
  8. /** Transform a stream into a stream where each chunk is divided by a newline,
  9. * be it `\n` or `\r\n`. `\r` can be enabled via the `allowCR` option.
  10. *
  11. * ```ts
  12. * import { TextLineStream } from 'https://deno.land/std@$STD_VERSION/streams/text_line_stream.ts';
  13. * const res = await fetch('https://example.com');
  14. * const lines = res.body!
  15. * .pipeThrough(new TextDecoderStream())
  16. * .pipeThrough(new TextLineStream());
  17. * ```
  18. */
  19. export class TextLineStream extends TransformStream<string, string> {
  20. private __allowCR: boolean;
  21. private __buf = '';
  22. constructor(options?: TextLineStreamOptions) {
  23. super({
  24. transform: (chunk, controller) => this.handle(chunk, controller),
  25. flush: (controller) => {
  26. if (this.__buf.length > 0) {
  27. if (
  28. this.__allowCR &&
  29. this.__buf[this.__buf.length - 1] === '\r'
  30. ) controller.enqueue(this.__buf.slice(0, -1));
  31. else controller.enqueue(this.__buf);
  32. }
  33. },
  34. });
  35. this.__allowCR = options?.allowCR ?? false;
  36. }
  37. private handle(chunk: string, controller: TransformStreamDefaultController<string>) {
  38. chunk = this.__buf + chunk;
  39. for (;;) {
  40. const lfIndex = chunk.indexOf('\n');
  41. if (this.__allowCR) {
  42. const crIndex = chunk.indexOf('\r');
  43. if (
  44. crIndex !== -1 && crIndex !== (chunk.length - 1) &&
  45. (lfIndex === -1 || (lfIndex - 1) > crIndex)
  46. ) {
  47. controller.enqueue(chunk.slice(0, crIndex));
  48. chunk = chunk.slice(crIndex + 1);
  49. continue;
  50. }
  51. }
  52. if (lfIndex !== -1) {
  53. let crOrLfIndex = lfIndex;
  54. if (chunk[lfIndex - 1] === '\r') {
  55. crOrLfIndex--;
  56. }
  57. controller.enqueue(chunk.slice(0, crOrLfIndex));
  58. chunk = chunk.slice(lfIndex + 1);
  59. continue;
  60. }
  61. break;
  62. }
  63. this.__buf = chunk;
  64. }
  65. }