contents.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import {getAsyncIterable} from './stream.js';
  2. export const getStreamContents = async (stream, {init, convertChunk, getSize, truncateChunk, addChunk, getFinalChunk, finalize}, {maxBuffer = Number.POSITIVE_INFINITY} = {}) => {
  3. const asyncIterable = getAsyncIterable(stream);
  4. const state = init();
  5. state.length = 0;
  6. try {
  7. for await (const chunk of asyncIterable) {
  8. const chunkType = getChunkType(chunk);
  9. const convertedChunk = convertChunk[chunkType](chunk, state);
  10. appendChunk({
  11. convertedChunk,
  12. state,
  13. getSize,
  14. truncateChunk,
  15. addChunk,
  16. maxBuffer,
  17. });
  18. }
  19. appendFinalChunk({
  20. state,
  21. convertChunk,
  22. getSize,
  23. truncateChunk,
  24. addChunk,
  25. getFinalChunk,
  26. maxBuffer,
  27. });
  28. return finalize(state);
  29. } catch (error) {
  30. const normalizedError = typeof error === 'object' && error !== null ? error : new Error(error);
  31. normalizedError.bufferedData = finalize(state);
  32. throw normalizedError;
  33. }
  34. };
  35. const appendFinalChunk = ({state, getSize, truncateChunk, addChunk, getFinalChunk, maxBuffer}) => {
  36. const convertedChunk = getFinalChunk(state);
  37. if (convertedChunk !== undefined) {
  38. appendChunk({
  39. convertedChunk,
  40. state,
  41. getSize,
  42. truncateChunk,
  43. addChunk,
  44. maxBuffer,
  45. });
  46. }
  47. };
  48. const appendChunk = ({convertedChunk, state, getSize, truncateChunk, addChunk, maxBuffer}) => {
  49. const chunkSize = getSize(convertedChunk);
  50. const newLength = state.length + chunkSize;
  51. if (newLength <= maxBuffer) {
  52. addNewChunk(convertedChunk, state, addChunk, newLength);
  53. return;
  54. }
  55. const truncatedChunk = truncateChunk(convertedChunk, maxBuffer - state.length);
  56. if (truncatedChunk !== undefined) {
  57. addNewChunk(truncatedChunk, state, addChunk, maxBuffer);
  58. }
  59. throw new MaxBufferError();
  60. };
  61. const addNewChunk = (convertedChunk, state, addChunk, newLength) => {
  62. state.contents = addChunk(convertedChunk, state, newLength);
  63. state.length = newLength;
  64. };
  65. const getChunkType = chunk => {
  66. const typeOfChunk = typeof chunk;
  67. if (typeOfChunk === 'string') {
  68. return 'string';
  69. }
  70. if (typeOfChunk !== 'object' || chunk === null) {
  71. return 'others';
  72. }
  73. if (globalThis.Buffer?.isBuffer(chunk)) {
  74. return 'buffer';
  75. }
  76. const prototypeName = objectToString.call(chunk);
  77. if (prototypeName === '[object ArrayBuffer]') {
  78. return 'arrayBuffer';
  79. }
  80. if (prototypeName === '[object DataView]') {
  81. return 'dataView';
  82. }
  83. if (
  84. Number.isInteger(chunk.byteLength)
  85. && Number.isInteger(chunk.byteOffset)
  86. && objectToString.call(chunk.buffer) === '[object ArrayBuffer]'
  87. ) {
  88. return 'typedArray';
  89. }
  90. return 'others';
  91. };
  92. const {toString: objectToString} = Object.prototype;
  93. export class MaxBufferError extends Error {
  94. name = 'MaxBufferError';
  95. constructor() {
  96. super('maxBuffer exceeded');
  97. }
  98. }