logo hsb.horse
← Voltar para o índice do blog

Blog

Função utilitária em TypeScript para converter valores em ReadableStream

Uma função utilitária que converte strings, objetos e outros valores em ReadableStream. Uma forma de lidar com streams tipados sem passar por Blob.

Publicado:

Ao converter valores primitivos como strings ou até objetos em ReadableStream, um caminho comum é transformar tudo em Blob primeiro.

O problema é que, quando o stream é gerado a partir de Blob, ele vira Uint8Array. Isso não é necessariamente errado, e para operações binárias provavelmente é até o caminho mais eficiente.

Mesmo assim, eu queria uma pequena função utilitária que também pudesse tratar strings e outros valores diretamente, então resolvi escrever uma.

Código

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();
},
});
}

Exemplos de uso

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>

Pontos

  • Usa Conditional Types para uma conversão segura em termos de tipo
  • Blob e ArrayBuffer viram streams de Uint8Array
  • string, number e valores parecidos permanecem com o tipo original
  • undefined, null e symbol são tratados como erro de tipo

Resumo

Ao fazer enqueue de valores diretamente em um ReadableStream, fica possível montar streams de forma mais flexível do que sempre passando por Blob.

A inferência de tipos também funciona bem, então essa utilidade acaba sendo prática no dia a dia com TypeScript.