text-line-transform-stream.ts 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  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. import { TransformStream } from 'node:stream/web';
  5. interface TextLineStreamOptions {
  6. /** Allow splitting by solo \r */
  7. allowCR?: boolean
  8. }
  9. /** Transform a stream into a stream where each chunk is divided by a newline,
  10. * be it `\n` or `\r\n`. `\r` can be enabled via the `allowCR` option.
  11. *
  12. * ```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 __buf = '';
  21. constructor({
  22. allowCR = false
  23. }: TextLineStreamOptions = {}) {
  24. let __buf = '';
  25. let chunkIndex = 0;
  26. super({
  27. transform(chunk, controller) {
  28. chunk = __buf + chunk;
  29. chunkIndex = 0;
  30. for (; ;) {
  31. const lfIndex = chunk.indexOf('\n', chunkIndex);
  32. if (allowCR) {
  33. const crIndex = chunk.indexOf('\r', chunkIndex);
  34. if (
  35. crIndex !== -1 && crIndex !== (chunk.length - 1)
  36. && (lfIndex === -1 || (lfIndex - 1) > crIndex)
  37. ) {
  38. controller.enqueue(chunk.slice(chunkIndex, crIndex));
  39. chunkIndex = crIndex + 1;
  40. continue;
  41. }
  42. }
  43. if (lfIndex !== -1) {
  44. let crOrLfIndex = lfIndex;
  45. if (chunk[lfIndex - 1] === '\r') {
  46. crOrLfIndex--;
  47. }
  48. controller.enqueue(chunk.slice(chunkIndex, crOrLfIndex));
  49. chunkIndex = lfIndex + 1;
  50. continue;
  51. }
  52. break;
  53. }
  54. __buf = chunk.slice(chunkIndex);
  55. },
  56. flush(controller) {
  57. if (__buf.length > 0) {
  58. // eslint-disable-next-line sukka/string/prefer-string-starts-ends-with -- performance
  59. if (allowCR && __buf[__buf.length - 1] === '\r') {
  60. controller.enqueue(__buf.slice(0, -1));
  61. } else {
  62. controller.enqueue(__buf);
  63. }
  64. }
  65. }
  66. });
  67. }
  68. }