1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @flow
    
  8.  */
    
  9. 
    
  10. export type Destination = ReadableStreamController;
    
  11. 
    
  12. export type PrecomputedChunk = Uint8Array;
    
  13. export opaque type Chunk = Uint8Array;
    
  14. export type BinaryChunk = Uint8Array;
    
  15. 
    
  16. export function scheduleWork(callback: () => void) {
    
  17.   callback();
    
  18. }
    
  19. 
    
  20. export function flushBuffered(destination: Destination) {
    
  21.   // WHATWG Streams do not yet have a way to flush the underlying
    
  22.   // transform streams. https://github.com/whatwg/streams/issues/960
    
  23. }
    
  24. 
    
  25. const VIEW_SIZE = 512;
    
  26. let currentView = null;
    
  27. let writtenBytes = 0;
    
  28. 
    
  29. export function beginWriting(destination: Destination) {
    
  30.   currentView = new Uint8Array(VIEW_SIZE);
    
  31.   writtenBytes = 0;
    
  32. }
    
  33. 
    
  34. export function writeChunk(
    
  35.   destination: Destination,
    
  36.   chunk: PrecomputedChunk | Chunk | BinaryChunk,
    
  37. ): void {
    
  38.   if (chunk.byteLength === 0) {
    
  39.     return;
    
  40.   }
    
  41. 
    
  42.   if (chunk.byteLength > VIEW_SIZE) {
    
  43.     if (__DEV__) {
    
  44.       if (precomputedChunkSet.has(chunk)) {
    
  45.         console.error(
    
  46.           'A large precomputed chunk was passed to writeChunk without being copied.' +
    
  47.             ' Large chunks get enqueued directly and are not copied. This is incompatible with precomputed chunks because you cannot enqueue the same precomputed chunk twice.' +
    
  48.             ' Use "cloneChunk" to make a copy of this large precomputed chunk before writing it. This is a bug in React.',
    
  49.         );
    
  50.       }
    
  51.     }
    
  52.     // this chunk may overflow a single view which implies it was not
    
  53.     // one that is cached by the streaming renderer. We will enqueu
    
  54.     // it directly and expect it is not re-used
    
  55.     if (writtenBytes > 0) {
    
  56.       destination.enqueue(
    
  57.         new Uint8Array(
    
  58.           ((currentView: any): Uint8Array).buffer,
    
  59.           0,
    
  60.           writtenBytes,
    
  61.         ),
    
  62.       );
    
  63.       currentView = new Uint8Array(VIEW_SIZE);
    
  64.       writtenBytes = 0;
    
  65.     }
    
  66.     destination.enqueue(chunk);
    
  67.     return;
    
  68.   }
    
  69. 
    
  70.   let bytesToWrite = chunk;
    
  71.   const allowableBytes = ((currentView: any): Uint8Array).length - writtenBytes;
    
  72.   if (allowableBytes < bytesToWrite.byteLength) {
    
  73.     // this chunk would overflow the current view. We enqueue a full view
    
  74.     // and start a new view with the remaining chunk
    
  75.     if (allowableBytes === 0) {
    
  76.       // the current view is already full, send it
    
  77.       destination.enqueue(currentView);
    
  78.     } else {
    
  79.       // fill up the current view and apply the remaining chunk bytes
    
  80.       // to a new view.
    
  81.       ((currentView: any): Uint8Array).set(
    
  82.         bytesToWrite.subarray(0, allowableBytes),
    
  83.         writtenBytes,
    
  84.       );
    
  85.       // writtenBytes += allowableBytes; // this can be skipped because we are going to immediately reset the view
    
  86.       destination.enqueue(currentView);
    
  87.       bytesToWrite = bytesToWrite.subarray(allowableBytes);
    
  88.     }
    
  89.     currentView = new Uint8Array(VIEW_SIZE);
    
  90.     writtenBytes = 0;
    
  91.   }
    
  92.   ((currentView: any): Uint8Array).set(bytesToWrite, writtenBytes);
    
  93.   writtenBytes += bytesToWrite.byteLength;
    
  94. }
    
  95. 
    
  96. export function writeChunkAndReturn(
    
  97.   destination: Destination,
    
  98.   chunk: PrecomputedChunk | Chunk | BinaryChunk,
    
  99. ): boolean {
    
  100.   writeChunk(destination, chunk);
    
  101.   // in web streams there is no backpressure so we can alwas write more
    
  102.   return true;
    
  103. }
    
  104. 
    
  105. export function completeWriting(destination: Destination) {
    
  106.   if (currentView && writtenBytes > 0) {
    
  107.     destination.enqueue(new Uint8Array(currentView.buffer, 0, writtenBytes));
    
  108.     currentView = null;
    
  109.     writtenBytes = 0;
    
  110.   }
    
  111. }
    
  112. 
    
  113. export function close(destination: Destination) {
    
  114.   destination.close();
    
  115. }
    
  116. 
    
  117. const textEncoder = new TextEncoder();
    
  118. 
    
  119. export function stringToChunk(content: string): Chunk {
    
  120.   return textEncoder.encode(content);
    
  121. }
    
  122. 
    
  123. const precomputedChunkSet: Set<Chunk | BinaryChunk> = __DEV__
    
  124.   ? new Set()
    
  125.   : (null: any);
    
  126. 
    
  127. export function stringToPrecomputedChunk(content: string): PrecomputedChunk {
    
  128.   const precomputedChunk = textEncoder.encode(content);
    
  129. 
    
  130.   if (__DEV__) {
    
  131.     precomputedChunkSet.add(precomputedChunk);
    
  132.   }
    
  133. 
    
  134.   return precomputedChunk;
    
  135. }
    
  136. 
    
  137. export function typedArrayToBinaryChunk(
    
  138.   content: $ArrayBufferView,
    
  139. ): BinaryChunk {
    
  140.   // Convert any non-Uint8Array array to Uint8Array. We could avoid this for Uint8Arrays.
    
  141.   // If we passed through this straight to enqueue we wouldn't have to convert it but since
    
  142.   // we need to copy the buffer in that case, we need to convert it to copy it.
    
  143.   // When we copy it into another array using set() it needs to be a Uint8Array.
    
  144.   const buffer = new Uint8Array(
    
  145.     content.buffer,
    
  146.     content.byteOffset,
    
  147.     content.byteLength,
    
  148.   );
    
  149.   // We clone large chunks so that we can transfer them when we write them.
    
  150.   // Others get copied into the target buffer.
    
  151.   return content.byteLength > VIEW_SIZE ? buffer.slice() : buffer;
    
  152. }
    
  153. 
    
  154. export function clonePrecomputedChunk(
    
  155.   precomputedChunk: PrecomputedChunk,
    
  156. ): PrecomputedChunk {
    
  157.   return precomputedChunk.byteLength > VIEW_SIZE
    
  158.     ? precomputedChunk.slice()
    
  159.     : precomputedChunk;
    
  160. }
    
  161. 
    
  162. export function byteLengthOfChunk(chunk: Chunk | PrecomputedChunk): number {
    
  163.   return chunk.byteLength;
    
  164. }
    
  165. 
    
  166. export function byteLengthOfBinaryChunk(chunk: BinaryChunk): number {
    
  167.   return chunk.byteLength;
    
  168. }
    
  169. 
    
  170. export function closeWithError(destination: Destination, error: mixed): void {
    
  171.   // $FlowFixMe[method-unbinding]
    
  172.   if (typeof destination.error === 'function') {
    
  173.     // $FlowFixMe[incompatible-call]: This is an Error object or the destination accepts other types.
    
  174.     destination.error(error);
    
  175.   } else {
    
  176.     // Earlier implementations doesn't support this method. In that environment you're
    
  177.     // supposed to throw from a promise returned but we don't return a promise in our
    
  178.     // approach. We could fork this implementation but this is environment is an edge
    
  179.     // case to begin with. It's even less common to run this in an older environment.
    
  180.     // Even then, this is not where errors are supposed to happen and they get reported
    
  181.     // to a global callback in addition to this anyway. So it's fine just to close this.
    
  182.     destination.close();
    
  183.   }
    
  184. }
    
  185. 
    
  186. export {createFastHashJS as createFastHash} from 'react-server/src/createFastHashJS';