0
# Stream Utilities
1
2
Utilities for converting between Node.js streams and Web Streams API, enabling seamless data flow between different stream types in Node.js environments.
3
4
## Capabilities
5
6
### Write ReadableStream to Writable
7
8
Writes a Web ReadableStream to a Node.js Writable stream with proper error handling and backpressure management.
9
10
```typescript { .api }
11
/**
12
* Writes a Web ReadableStream to a Node.js Writable stream
13
* @param stream - The ReadableStream to read from
14
* @param writable - The Node.js Writable stream to write to
15
* @returns Promise that resolves when the operation completes
16
*/
17
function writeReadableStreamToWritable(
18
stream: ReadableStream,
19
writable: Writable
20
): Promise<void>;
21
```
22
23
**Usage Examples:**
24
25
```typescript
26
import { writeReadableStreamToWritable } from "@remix-run/node";
27
import { createWriteStream } from "node:fs";
28
29
// Save a fetch response to file
30
export async function action({ request }: ActionFunctionArgs) {
31
const response = await fetch("https://example.com/large-file.zip");
32
33
if (response.body) {
34
const fileStream = createWriteStream("/tmp/downloaded-file.zip");
35
36
await writeReadableStreamToWritable(response.body, fileStream);
37
38
return json({ message: "File downloaded successfully" });
39
}
40
41
return json({ error: "No response body" }, { status: 400 });
42
}
43
44
// Stream request body to file
45
export async function action({ request }: ActionFunctionArgs) {
46
if (request.body) {
47
const outputStream = createWriteStream("/tmp/request-body.bin");
48
49
try {
50
await writeReadableStreamToWritable(request.body, outputStream);
51
return json({ success: true });
52
} catch (error) {
53
return json({ error: "Failed to save request body" }, { status: 500 });
54
}
55
}
56
57
return json({ error: "No request body" }, { status: 400 });
58
}
59
```
60
61
### Write AsyncIterable to Writable
62
63
Writes an AsyncIterable of Uint8Array chunks to a Node.js Writable stream.
64
65
```typescript { .api }
66
/**
67
* Writes an AsyncIterable to a Node.js Writable stream
68
* @param iterable - The AsyncIterable of Uint8Array chunks to write
69
* @param writable - The Node.js Writable stream to write to
70
* @returns Promise that resolves when the operation completes
71
*/
72
function writeAsyncIterableToWritable(
73
iterable: AsyncIterable<Uint8Array>,
74
writable: Writable
75
): Promise<void>;
76
```
77
78
**Usage Examples:**
79
80
```typescript
81
import { writeAsyncIterableToWritable } from "@remix-run/node";
82
import { createWriteStream } from "node:fs";
83
84
// Process and save async data
85
async function* generateData() {
86
for (let i = 0; i < 1000; i++) {
87
yield new TextEncoder().encode(`Data chunk ${i}\n`);
88
}
89
}
90
91
export async function action() {
92
const dataStream = generateData();
93
const fileStream = createWriteStream("/tmp/generated-data.txt");
94
95
await writeAsyncIterableToWritable(dataStream, fileStream);
96
97
return json({ message: "Data generated and saved" });
98
}
99
```
100
101
### ReadableStream to String
102
103
Converts a Web ReadableStream to a string with optional encoding specification.
104
105
```typescript { .api }
106
/**
107
* Converts a ReadableStream to a string
108
* @param stream - The ReadableStream to convert
109
* @param encoding - Optional buffer encoding (defaults to 'utf8')
110
* @returns Promise resolving to the stream contents as a string
111
*/
112
function readableStreamToString(
113
stream: ReadableStream<Uint8Array>,
114
encoding?: BufferEncoding
115
): Promise<string>;
116
```
117
118
**Usage Examples:**
119
120
```typescript
121
import { readableStreamToString } from "@remix-run/node";
122
123
// Read request body as string
124
export async function action({ request }: ActionFunctionArgs) {
125
if (request.body) {
126
const bodyText = await readableStreamToString(request.body);
127
128
try {
129
const data = JSON.parse(bodyText);
130
return json({ received: data });
131
} catch (error) {
132
return json({ error: "Invalid JSON" }, { status: 400 });
133
}
134
}
135
136
return json({ error: "No request body" }, { status: 400 });
137
}
138
139
// Read fetch response as string
140
export async function loader() {
141
const response = await fetch("https://api.example.com/data");
142
143
if (response.body) {
144
const text = await readableStreamToString(response.body);
145
return json({ data: text });
146
}
147
148
return json({ error: "No response body" }, { status: 500 });
149
}
150
151
// Read with specific encoding
152
export async function action({ request }: ActionFunctionArgs) {
153
if (request.body) {
154
const bodyText = await readableStreamToString(request.body, "base64");
155
return json({ encodedData: bodyText });
156
}
157
158
return json({ error: "No request body" }, { status: 400 });
159
}
160
```
161
162
### Create ReadableStream from Readable
163
164
Converts a Node.js Readable stream to a Web ReadableStream with proper backpressure handling.
165
166
```typescript { .api }
167
/**
168
* Creates a Web ReadableStream from a Node.js Readable stream
169
* @param source - The Node.js Readable stream to convert
170
* @returns Web ReadableStream
171
*/
172
function createReadableStreamFromReadable(
173
source: Readable & { readableHighWaterMark?: number }
174
): ReadableStream;
175
```
176
177
**Usage Examples:**
178
179
```typescript
180
import { createReadableStreamFromReadable } from "@remix-run/node";
181
import { createReadStream } from "node:fs";
182
183
// Stream file contents as Web ReadableStream
184
export async function loader({ params }: LoaderFunctionArgs) {
185
const filePath = `/files/${params.filename}`;
186
const nodeStream = createReadStream(filePath);
187
const webStream = createReadableStreamFromReadable(nodeStream);
188
189
return new Response(webStream, {
190
headers: {
191
"Content-Type": "application/octet-stream",
192
"Content-Disposition": `attachment; filename="${params.filename}"`
193
}
194
});
195
}
196
197
// Convert process.stdin to Web ReadableStream
198
export async function action() {
199
const stdinStream = createReadableStreamFromReadable(process.stdin);
200
const reader = stdinStream.getReader();
201
202
const chunks: string[] = [];
203
while (true) {
204
const { done, value } = await reader.read();
205
if (done) break;
206
207
chunks.push(new TextDecoder().decode(value));
208
}
209
210
return json({ input: chunks.join("") });
211
}
212
```
213
214
## Stream Conversion Patterns
215
216
**Node.js to Web Streams:**
217
218
```typescript
219
import { createReadableStreamFromReadable } from "@remix-run/node";
220
import { createReadStream } from "node:fs";
221
222
// File → Web ReadableStream
223
const fileStream = createReadStream("data.txt");
224
const webStream = createReadableStreamFromReadable(fileStream);
225
```
226
227
**Web Streams to Node.js:**
228
229
```typescript
230
import { writeReadableStreamToWritable, readableStreamToString } from "@remix-run/node";
231
import { createWriteStream } from "node:fs";
232
233
// Web ReadableStream → File
234
const response = await fetch("https://example.com/data");
235
if (response.body) {
236
await writeReadableStreamToWritable(response.body, createWriteStream("output.txt"));
237
}
238
239
// Web ReadableStream → String
240
const text = await readableStreamToString(response.body);
241
```
242
243
**Performance Considerations:**
244
245
- **Backpressure handling**: All utilities properly handle backpressure to prevent memory issues
246
- **Streaming processing**: Data is processed in chunks to handle large streams efficiently
247
- **Error propagation**: Errors are properly propagated between stream types
248
- **Resource cleanup**: Streams are properly closed and cleaned up on completion or error