10
11
'use strict';
12
13
-// Polyfills for test environment
14
-global.ReadableStream =
15
- require('web-streams-polyfill/ponyfill/es6').ReadableStream;
16
-global.WritableStream =
17
- require('web-streams-polyfill/ponyfill/es6').WritableStream;
18
-global.TextEncoder = require('util').TextEncoder;
19
-global.TextDecoder = require('util').TextDecoder;
20
-global.Blob = require('buffer').Blob;
21
-if (typeof File === 'undefined' || typeof FormData === 'undefined') {
22
- global.File = require('buffer').File || require('undici').File;
23
- global.FormData = require('undici').FormData;
24
-}
13
// Patch for Edge environments for global scope
14
global.AsyncLocalStorage = require('async_hooks').AsyncLocalStorage;
15
115
chunk.set(prevChunk, 0);
116
chunk.set(value, prevChunk.length);
117
if (chunk.length > 50) {
118
+ // Copy the part we're keeping (prevChunk) to avoid buffer
119
+ // transfer. When we enqueue the partial chunk below, downstream
120
+ // consumers (like byte streams in the Flight Client) may detach
121
+ // the underlying buffer. Since prevChunk would share the same
122
+ // buffer, we copy it first so it has its own independent buffer.
123
+ // TODO: Should we just use {type: 'bytes'} for this stream to
124
+ // always transfer ownership, and not only "accidentally" when we
125
+ // enqueue in the Flight Client?
126
+ prevChunk = chunk.slice(chunk.length - 50);
127
controller.enqueue(chunk.subarray(0, chunk.length - 50));
131
- prevChunk = chunk.subarray(chunk.length - 50);
128
} else {
129
// Wait to see if we get some more bytes to join in.
130
prevChunk = chunk;
1114
expect(streamedBuffers).toEqual(buffers);
1115
});
1116
1117
+ it('should support binary ReadableStreams', async () => {
1118
+ const encoder = new TextEncoder();
1119
+ const words = ['Hello', 'streaming', 'world'];
1120
+
1121
+ const stream = new ReadableStream({
1122
+ type: 'bytes',
1123
+ async start(controller) {
1124
+ for (let i = 0; i < words.length; i++) {
1125
+ const chunk = encoder.encode(words[i] + ' ');
1126
+ controller.enqueue(chunk);
1127
+ }
1128
+ controller.close();
1129
+ },
1130
+ });
1131
+
1132
+ const rscStream = await serverAct(() =>
1133
+ ReactServerDOMServer.renderToReadableStream(stream, {}),
1134
+ );
1135
+
1136
+ const result = await ReactServerDOMClient.createFromReadableStream(
1137
+ rscStream,
1138
+ {
1139
+ serverConsumerManifest: {
1140
+ moduleMap: null,
1141
+ moduleLoading: null,
1142
+ },
1143
+ },
1144
+ );
1145
+
1146
+ const reader = result.getReader();
1147
+ const decoder = new TextDecoder();
1148
+
1149
+ let text = '';
1150
+ let entry;
1151
+ while (!(entry = await reader.read()).done) {
1152
+ text += decoder.decode(entry.value);
1153
+ }
1154
+
1155
+ expect(text).toBe('Hello streaming world ');
1156
+ });
1157
+
1158
+ it('should support large binary ReadableStreams', async () => {
1159
+ const chunkCount = 100;
1160
+ const chunkSize = 1024;
1161
+ const expectedBytes = [];
1162
+
1163
+ const stream = new ReadableStream({
1164
+ type: 'bytes',
1165
+ start(controller) {
1166
+ for (let i = 0; i < chunkCount; i++) {
1167
+ const chunk = new Uint8Array(chunkSize);
1168
+ for (let j = 0; j < chunkSize; j++) {
1169
+ chunk[j] = (i + j) % 256;
1170
+ }
1171
+ expectedBytes.push(...Array.from(chunk));
1172
+ controller.enqueue(chunk);
1173
+ }
1174
+ controller.close();
1175
+ },
1176
+ });
1177
+
1178
+ const rscStream = await serverAct(() =>
1179
+ ReactServerDOMServer.renderToReadableStream(stream, {}),
1180
+ );
1181
+
1182
+ const result = await ReactServerDOMClient.createFromReadableStream(
1183
+ // Use passThrough to split and rejoin chunks at arbitrary boundaries.
1184
+ passThrough(rscStream),
1185
+ {
1186
+ serverConsumerManifest: {
1187
+ moduleMap: null,
1188
+ moduleLoading: null,
1189
+ },
1190
+ },
1191
+ );
1192
+
1193
+ const reader = result.getReader();
1194
+ const receivedBytes = [];
1195
+ let entry;
1196
+ while (!(entry = await reader.read()).done) {
1197
+ expect(entry.value instanceof Uint8Array).toBe(true);
1198
+ receivedBytes.push(...Array.from(entry.value));
1199
+ }
1200
+
1201
+ expect(receivedBytes).toEqual(expectedBytes);
1202
+ });
1203
+
1204
it('should support BYOB binary ReadableStreams', async () => {
1122
- const buffer = new Uint8Array([
1205
+ const sourceBytes = [
1206
123, 4, 10, 5, 100, 255, 244, 45, 56, 67, 43, 124, 67, 89, 100, 20,
1124
- ]).buffer;
1207
+ ];
1208
+
1209
+ // Create separate buffers for each typed array to avoid ArrayBuffer
1210
+ // transfer issues. Each view needs its own buffer because enqueue()
1211
+ // transfers ownership.
1212
const buffers = [
1126
- new Int8Array(buffer, 1),
1127
- new Uint8Array(buffer, 2),
1128
- new Uint8ClampedArray(buffer, 2),
1129
- new Int16Array(buffer, 2),
1130
- new Uint16Array(buffer, 2),
1131
- new Int32Array(buffer, 4),
1132
- new Uint32Array(buffer, 4),
1133
- new Float32Array(buffer, 4),
1134
- new Float64Array(buffer, 0),
1135
- new BigInt64Array(buffer, 0),
1136
- new BigUint64Array(buffer, 0),
1137
- new DataView(buffer, 3),
1213
+ new Int8Array(sourceBytes.slice(1)),
1214
+ new Uint8Array(sourceBytes.slice(2)),
1215
+ new Uint8ClampedArray(sourceBytes.slice(2)),
1216
+ new Int16Array(new Uint8Array(sourceBytes.slice(2)).buffer),
1217
+ new Uint16Array(new Uint8Array(sourceBytes.slice(2)).buffer),
1218
+ new Int32Array(new Uint8Array(sourceBytes.slice(4)).buffer),
1219
+ new Uint32Array(new Uint8Array(sourceBytes.slice(4)).buffer),
1220
+ new Float32Array(new Uint8Array(sourceBytes.slice(4)).buffer),
1221
+ new Float64Array(new Uint8Array(sourceBytes.slice(0)).buffer),
1222
+ new BigInt64Array(new Uint8Array(sourceBytes.slice(0)).buffer),
1223
+ new BigUint64Array(new Uint8Array(sourceBytes.slice(0)).buffer),
1224
+ new DataView(new Uint8Array(sourceBytes.slice(3)).buffer),
1225
];
1226
1227
+ // Save expected bytes before enqueueing (which will detach the buffers).
1228
+ const expectedBytes = buffers.flatMap(c =>
1229
+ Array.from(new Uint8Array(c.buffer, c.byteOffset, c.byteLength)),
1230
+ );
1231
+
1232
// This a binary stream where each chunk ends up as Uint8Array.
1233
const s = new ReadableStream({
1234
type: 'bytes',
1268
1269
// The streamed buffers might be in different chunks and in Uint8Array form but
1270
// the concatenated bytes should be the same.
1179
- expect(streamedBuffers.flatMap(t => Array.from(t))).toEqual(
1180
- buffers.flatMap(c =>
1181
- Array.from(new Uint8Array(c.buffer, c.byteOffset, c.byteLength)),
1182
- ),
1183
- );
1271
+ expect(streamedBuffers.flatMap(t => Array.from(t))).toEqual(expectedBytes);
1272
});
1273
1274
// @gate !__DEV__ || enableComponentPerformanceTrack