When converting primitive values such as strings or plain objects into a ReadableStream, a common approach is to turn them into a Blob first.
The problem is that when a stream is created through Blob, it becomes Uint8Array. That is not necessarily wrong, and binary operations are probably the most efficient path anyway.
Still, I wanted a small utility that can also treat strings and other values directly, so I wrote one.
Code
type StreamResource<T> = T extends Blob ? Uint8Array : T extends Uint8Array | ArrayBuffer ? Uint8Array : T extends undefined | null | symbol ? never : T;
function toReadableStream<T>(value: T): ReadableStream<StreamResource<T>> { if (value === undefined || value === null || typeof value === "symbol") { throw new TypeError( "Cannot convert undefined, null, or symbol to ReadableStream" ); }
if (value instanceof Blob) { return value.stream() as ReadableStream<StreamResource<T>>; }
if (value instanceof Uint8Array || value instanceof ArrayBuffer) { const uint8Array = new Uint8Array(value); return new ReadableStream<Uint8Array>({ start(controller) { controller.enqueue(uint8Array); controller.close(); }, }) as ReadableStream<StreamResource<T>>; }
return new ReadableStream<StreamResource<T>>({ start(controller) { controller.enqueue(value as StreamResource<T>); controller.close(); }, });}Usage examples
const blob = new Blob(['Hello, World!'], { type: 'text/plain' });const blobStream = toReadableStream(blob);// ^? ReadableStream<Uint8Array>
const buffer = new ArrayBuffer(8);const bufferStream = toReadableStream(buffer);// ^? ReadableStream<Uint8Array>
const uint8Array = new Uint8Array([1, 2, 3, 4]);const uint8ArrayStream = toReadableStream(uint8Array);// ^? ReadableStream<Uint8Array>
const str = 'Hello, TypeScript!';const strStream = toReadableStream(str);// ^? ReadableStream<string>
const num = 42;const numStream = toReadableStream(num);// ^? ReadableStream<number>
const undefStream = toReadableStream(undefined);// ^? ReadableStream<never>Points
- Uses conditional types for type-safe conversion
BlobandArrayBufferbecomeUint8Arraystreamsstring,number, and similar values stay as their original typesundefined,null, andsymbolare treated as type errors
Summary
By enqueueing values directly into a ReadableStream, you can build streams more flexibly than by always going through Blob.
Type inference also works well, so it ends up as a handy utility in TypeScript.
hsb.horse