85 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			85 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import {getStreamContents} from './contents.js';
 | |
| import {noop, throwObjectStream, getLengthProperty} from './utils.js';
 | |
| 
 | |
| export async function getStreamAsArrayBuffer(stream, options) {
 | |
| 	return getStreamContents(stream, arrayBufferMethods, options);
 | |
| }
 | |
| 
 | |
| const initArrayBuffer = () => ({contents: new ArrayBuffer(0)});
 | |
| 
 | |
| const useTextEncoder = chunk => textEncoder.encode(chunk);
 | |
| const textEncoder = new TextEncoder();
 | |
| 
 | |
| const useUint8Array = chunk => new Uint8Array(chunk);
 | |
| 
 | |
| const useUint8ArrayWithOffset = chunk => new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
 | |
| 
 | |
| const truncateArrayBufferChunk = (convertedChunk, chunkSize) => convertedChunk.slice(0, chunkSize);
 | |
| 
 | |
| // `contents` is an increasingly growing `Uint8Array`.
 | |
| const addArrayBufferChunk = (convertedChunk, {contents, length: previousLength}, length) => {
 | |
| 	const newContents = hasArrayBufferResize() ? resizeArrayBuffer(contents, length) : resizeArrayBufferSlow(contents, length);
 | |
| 	new Uint8Array(newContents).set(convertedChunk, previousLength);
 | |
| 	return newContents;
 | |
| };
 | |
| 
 | |
| // Without `ArrayBuffer.resize()`, `contents` size is always a power of 2.
 | |
| // This means its last bytes are zeroes (not stream data), which need to be
 | |
| // trimmed at the end with `ArrayBuffer.slice()`.
 | |
| const resizeArrayBufferSlow = (contents, length) => {
 | |
| 	if (length <= contents.byteLength) {
 | |
| 		return contents;
 | |
| 	}
 | |
| 
 | |
| 	const arrayBuffer = new ArrayBuffer(getNewContentsLength(length));
 | |
| 	new Uint8Array(arrayBuffer).set(new Uint8Array(contents), 0);
 | |
| 	return arrayBuffer;
 | |
| };
 | |
| 
 | |
| // With `ArrayBuffer.resize()`, `contents` size matches exactly the size of
 | |
| // the stream data. It does not include extraneous zeroes to trim at the end.
 | |
| // The underlying `ArrayBuffer` does allocate a number of bytes that is a power
 | |
| // of 2, but those bytes are only visible after calling `ArrayBuffer.resize()`.
 | |
| const resizeArrayBuffer = (contents, length) => {
 | |
| 	if (length <= contents.maxByteLength) {
 | |
| 		contents.resize(length);
 | |
| 		return contents;
 | |
| 	}
 | |
| 
 | |
| 	const arrayBuffer = new ArrayBuffer(length, {maxByteLength: getNewContentsLength(length)});
 | |
| 	new Uint8Array(arrayBuffer).set(new Uint8Array(contents), 0);
 | |
| 	return arrayBuffer;
 | |
| };
 | |
| 
 | |
| // Retrieve the closest `length` that is both >= and a power of 2
 | |
| const getNewContentsLength = length => SCALE_FACTOR ** Math.ceil(Math.log(length) / Math.log(SCALE_FACTOR));
 | |
| 
 | |
| const SCALE_FACTOR = 2;
 | |
| 
 | |
| const finalizeArrayBuffer = ({contents, length}) => hasArrayBufferResize() ? contents : contents.slice(0, length);
 | |
| 
 | |
| // `ArrayBuffer.slice()` is slow. When `ArrayBuffer.resize()` is available
 | |
| // (Node >=20.0.0, Safari >=16.4 and Chrome), we can use it instead.
 | |
| // eslint-disable-next-line no-warning-comments
 | |
| // TODO: remove after dropping support for Node 20.
 | |
| // eslint-disable-next-line no-warning-comments
 | |
| // TODO: use `ArrayBuffer.transferToFixedLength()` instead once it is available
 | |
| const hasArrayBufferResize = () => 'resize' in ArrayBuffer.prototype;
 | |
| 
 | |
| const arrayBufferMethods = {
 | |
| 	init: initArrayBuffer,
 | |
| 	convertChunk: {
 | |
| 		string: useTextEncoder,
 | |
| 		buffer: useUint8Array,
 | |
| 		arrayBuffer: useUint8Array,
 | |
| 		dataView: useUint8ArrayWithOffset,
 | |
| 		typedArray: useUint8ArrayWithOffset,
 | |
| 		others: throwObjectStream,
 | |
| 	},
 | |
| 	getSize: getLengthProperty,
 | |
| 	truncateChunk: truncateArrayBufferChunk,
 | |
| 	addChunk: addArrayBufferChunk,
 | |
| 	getFinalChunk: noop,
 | |
| 	finalize: finalizeArrayBuffer,
 | |
| };
 |