Node.js platform abstractions and utilities for Remix applications
—
Utilities for converting between Node.js streams and Web Streams API, enabling seamless data flow between different stream types in Node.js environments.
Writes a Web ReadableStream to a Node.js Writable stream with proper error handling and backpressure management.
/**
* Writes a Web ReadableStream to a Node.js Writable stream
* @param stream - The ReadableStream to read from
* @param writable - The Node.js Writable stream to write to
* @returns Promise that resolves when the operation completes
*/
function writeReadableStreamToWritable(
stream: ReadableStream,
writable: Writable
): Promise<void>;Usage Examples:
import { writeReadableStreamToWritable } from "@remix-run/node";
import { createWriteStream } from "node:fs";
// Save a fetch response to file
export async function action({ request }: ActionFunctionArgs) {
const response = await fetch("https://example.com/large-file.zip");
if (response.body) {
const fileStream = createWriteStream("/tmp/downloaded-file.zip");
await writeReadableStreamToWritable(response.body, fileStream);
return json({ message: "File downloaded successfully" });
}
return json({ error: "No response body" }, { status: 400 });
}
// Stream request body to file
export async function action({ request }: ActionFunctionArgs) {
if (request.body) {
const outputStream = createWriteStream("/tmp/request-body.bin");
try {
await writeReadableStreamToWritable(request.body, outputStream);
return json({ success: true });
} catch (error) {
return json({ error: "Failed to save request body" }, { status: 500 });
}
}
return json({ error: "No request body" }, { status: 400 });
}Writes an AsyncIterable of Uint8Array chunks to a Node.js Writable stream.
/**
* Writes an AsyncIterable to a Node.js Writable stream
* @param iterable - The AsyncIterable of Uint8Array chunks to write
* @param writable - The Node.js Writable stream to write to
* @returns Promise that resolves when the operation completes
*/
function writeAsyncIterableToWritable(
iterable: AsyncIterable<Uint8Array>,
writable: Writable
): Promise<void>;Usage Examples:
import { writeAsyncIterableToWritable } from "@remix-run/node";
import { createWriteStream } from "node:fs";
// Process and save async data
async function* generateData() {
for (let i = 0; i < 1000; i++) {
yield new TextEncoder().encode(`Data chunk ${i}\n`);
}
}
export async function action() {
const dataStream = generateData();
const fileStream = createWriteStream("/tmp/generated-data.txt");
await writeAsyncIterableToWritable(dataStream, fileStream);
return json({ message: "Data generated and saved" });
}Converts a Web ReadableStream to a string with optional encoding specification.
/**
* Converts a ReadableStream to a string
* @param stream - The ReadableStream to convert
* @param encoding - Optional buffer encoding (defaults to 'utf8')
* @returns Promise resolving to the stream contents as a string
*/
function readableStreamToString(
stream: ReadableStream<Uint8Array>,
encoding?: BufferEncoding
): Promise<string>;Usage Examples:
import { readableStreamToString } from "@remix-run/node";
// Read request body as string
export async function action({ request }: ActionFunctionArgs) {
if (request.body) {
const bodyText = await readableStreamToString(request.body);
try {
const data = JSON.parse(bodyText);
return json({ received: data });
} catch (error) {
return json({ error: "Invalid JSON" }, { status: 400 });
}
}
return json({ error: "No request body" }, { status: 400 });
}
// Read fetch response as string
export async function loader() {
const response = await fetch("https://api.example.com/data");
if (response.body) {
const text = await readableStreamToString(response.body);
return json({ data: text });
}
return json({ error: "No response body" }, { status: 500 });
}
// Read with specific encoding
export async function action({ request }: ActionFunctionArgs) {
if (request.body) {
const bodyText = await readableStreamToString(request.body, "base64");
return json({ encodedData: bodyText });
}
return json({ error: "No request body" }, { status: 400 });
}Converts a Node.js Readable stream to a Web ReadableStream with proper backpressure handling.
/**
* Creates a Web ReadableStream from a Node.js Readable stream
* @param source - The Node.js Readable stream to convert
* @returns Web ReadableStream
*/
function createReadableStreamFromReadable(
source: Readable & { readableHighWaterMark?: number }
): ReadableStream;Usage Examples:
import { createReadableStreamFromReadable } from "@remix-run/node";
import { createReadStream } from "node:fs";
// Stream file contents as Web ReadableStream
export async function loader({ params }: LoaderFunctionArgs) {
const filePath = `/files/${params.filename}`;
const nodeStream = createReadStream(filePath);
const webStream = createReadableStreamFromReadable(nodeStream);
return new Response(webStream, {
headers: {
"Content-Type": "application/octet-stream",
"Content-Disposition": `attachment; filename="${params.filename}"`
}
});
}
// Convert process.stdin to Web ReadableStream
export async function action() {
const stdinStream = createReadableStreamFromReadable(process.stdin);
const reader = stdinStream.getReader();
const chunks: string[] = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(new TextDecoder().decode(value));
}
return json({ input: chunks.join("") });
}Node.js to Web Streams:
import { createReadableStreamFromReadable } from "@remix-run/node";
import { createReadStream } from "node:fs";
// File → Web ReadableStream
const fileStream = createReadStream("data.txt");
const webStream = createReadableStreamFromReadable(fileStream);Web Streams to Node.js:
import { writeReadableStreamToWritable, readableStreamToString } from "@remix-run/node";
import { createWriteStream } from "node:fs";
// Web ReadableStream → File
const response = await fetch("https://example.com/data");
if (response.body) {
await writeReadableStreamToWritable(response.body, createWriteStream("output.txt"));
}
// Web ReadableStream → String
const text = await readableStreamToString(response.body);Performance Considerations:
Install with Tessl CLI
npx tessl i tessl/npm-remix-run--node